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.