SetState is the most frequently used API in React. Here we briefly share its implementation mechanism. The source code is based on React V16.4.3-alpha. 0

First, prepare knowledge

1, fiber

There are a lot of articles on fiber on the Internet that mostly describe the algorithm of fiber. In fact, Fiber contains data structures and algorithms. As understood in previous versions of V16, Fiber represented a node of the virtual DOM in the source code

The React event system

Does not call the addEventListener to bind the event to the corresponding node as vue does. Instead, it is bound to the document by event delegate. Let’s take a quick look at the implementation:

Const someElement = document.getelementById (const someElement = document.getelementById ('#someId'Dir (someElement) // Any DOM node rendered by react will have the attribute '__reactEventHandlers****' Console. dir(someeld.__reacteventhandlers ****) // __reactEventHandlers can be found in the JSX event attribute added for this tag const onClick = someElement.__reactEventHandlers****.onClickCopy the code

With that in mind, let’s take a quick look at the React event system

  • Clicking a button triggers the Click event on the Document
  • Get the event object event
  • Event. Target tells you which button is clicked
  • Get the one above the button__reactEventHandlers
  • And then you have onClick
/ / pseudo-code documnet. AddEventListener ('click'.function(event){ const target = event.target const onClick = traget.__reactEventHandlers*****.onClick // IsBatchingUpdates behind global variables can explain the specific var previousIsBatchingUpdates = isBatchingUpdates; isBatchingUpdates =true; Try {// Perform the event callbackreturn onClick(event);
    } finally {
        isBatchingUpdates = previousIsBatchingUpdates;
        performSyncWork()
    }
})
Copy the code

This is a brief description; the actual implementation is much more complicated

Two, go through the source code

1. SetState implementation

You can see the source code here

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Copy the code

2, this updater

Where does this. Updater get assigned we don’t care about that for now, just that it gets assigned to classComponentUpdater

3, classComponentUpdater

We can look at the source code here and just care that the update is generated, inserted into the Update queue, and then called scheduleWork

// const ClassentUpdater = {... enqueueSetState(inst, payload, callback) { const update = createUpdate(expirationTime); //setState(payload, callback); update.payload = payload; update.callback = callback; // Insert enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); },...Copy the code

4, the scheduleWork

So this is where we look at the source and we just have to worry about the next piece of logic

"// isWorking" and "isresearch" are all variables, which we analyze laterif(! isWorking || isCommitting || nextRoot ! == root ) { const rootExpirationTime = root.expirationTime; requestWork(root, rootExpirationTime); }Copy the code

5, requestWork

functionRequestWork (root, expirationTime) {// Add a root node to a context task addRootToSchedule(root, expirationTime) We'll look at that laterif (isRendering) {
    return; } // isBatchingUpdates, isUnbatchingUpdates, isBatchingUpdates are global variablesif (isBatchingUpdates) {
    if (isUnbatchingUpdates) {
      ....
      performWorkOnRoot(root, Sync, false);
    }
    return;
  }
  
  if (expirationTime === Sync) {
    performSyncWork();
  } else{ scheduleCallbackWithExpirationTime(root, expirationTime); }}Copy the code

Ok, to understand the process of setState, you can trace these five steps. The following will be a specific analysis of the whole process based on specific scenarios

Third, use scenarios

1. Interactive events

handleClick(){
    this.setState({
        name: Daniel Wu}) console.log(this.state.name) // >> dog this. SetState ({age:'18'
    })
    console.log(this.state.age) // >> 40
}
Copy the code

In section 1, you learned that isBatchingUpdates = true before executing the event callback to handleClick. Scroll to section 2 to see the source code process, which is finally executed in Step 5 in requestWork

function requestWork(){
    ...
    if (isBatchingUpdates) {
        return}... }Copy the code

So that’s where the first setState gets returned, and then the second setState gets returned up to that point. Now what do we know?

SetState in an interaction event only creates a new update and then adds it to enqueueUpdate. SetState does not directly trigger the React update

When handleClick executes, it immediately calls performWork to start the React update process

For example, since isBatchingUpdates = true will collect all updates to enqueueUpdate, performWork will be called once the interaction event callback is executed to update all state

Now let’s think about the question, setState is asynchronous, right?

From the source code, you can see that the entire process is synchronized to the browser, step by step; For developers, setState delays react updates because of batch operations

2. SetTimeout, setInterval, Promise

In 1, we know that we can’t get the new state directly after executing setState because isBatchingUpdates = true. What if we can avoid isBatchingUpdates

handleClick() {setTimeout(() => {
       this.setState({
           name: Daniel Wu}) console.log(this.state.name) // >> Daniel This. SetState ({age:'18'
       })
       console.log(this.state.age) // >> 18
   })
}
Copy the code

Using setTimeout to execute setState, there’s nothing wrong with the React event system. IsBatchingUpdates are false by default. See section 2, Step 5. Each time setState executes performSyncWork to trigger the React update, so every time setState is called we get the latest state

By executing setState in setTimeout, we achieve the effect that setState is synchronous. Of course, setInterval and Promise can also achieve the same effect.

3, componentWillUpdate (render life cycle)

Just one more thing

Notice three global variables in the second section of the source code: isRendering, isWorking, and isresearch (right)

The REACT update in V16 has two phases: the RECONCiler and COMMIT phases

  • IsRendering: True when react updates
  • IsWorking: True when it is in the Reconciler phase and true when it is in the COMMIT phase
  • Isresearch: True when you enter the commit phase

The life cycle before render belongs to the Reconciler stage: isRendering = true, isWorking = true

Trigger Step 5 of Section 2:

function requestWork(){
    ...
    if (isRendering) {
        return}... }Copy the code

The life cycle does not trigger a new update before render, it just adds the new update to the enqueueUpdate tail and processes it in the current update task

4, componentDidUpdate (render after life cycle)

“Commit” (isRendering = true, isWorking = true, isresearch = true) also trigger step 5 in Section 2:

function requestWork(){
    addRootToSchedule(root, expirationTime)
    if (isRendering) {
        return}... }Copy the code

The life cycle will not trigger a new update immediately after render, and it will not be processed in this update task. Here we note that there is an addRootToSchedule(root, expirationTime) that sets the new update as the next update task

Ex. :

Modifying name triggers componentDidUpdate() to modify age at componentDidUpdate

Process: Change the name to start the React Update process to complete the Reconciler and COMMIT phases, because there is also a task to change the AGE in the task, and start the React Update process again to complete the Reconciler and COMMIT phases

Note: Using setState on componentDidUpdate may cause an infinite loop

At the end

React itself is not used very much, so I can only think of the above four scenarios based on official documents. In order to explain setState only, fiber-related process is deliberately omitted in the paper, and fiber-related sharing will be available later. Have any suggestion welcome to leave a message below exchange.