This is my fifth article dissecting React source code. This article will start with you to learn the component update process related content, as far as possible from the source code to understand the principle, reduce the difficulty of learning.
Article related Information
- React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6
- A collection of all previous articles
What can you learn from the component update process?
The article is divided into three parts. In this part of the article, you can learn the following:
- How is the batch update behind setState implemented
- What is Fiber? What’s the use?
- How to Schedule tasks
In the other two articles you can learn how to mix and render components.
How is the batch update behind setState implemented
As you all know, in most cases multiple setStates do not trigger multiple renderings, and the value of state is not real-time, so this can reduce unnecessary performance costs.
handleClick () {
// Initialize 'count' to 0
console.log(this.state.count) / / - > 0
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) / / - > 0
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) / / - > 0
}
Copy the code
So how does this behavior work? The answer is batch updating. Let’s learn how batch updates are implemented.
The principle behind this is pretty simple. If handleClick were triggered by a click event, handleClick would be wrapped in something like this:
isBatchingUpdates = true
try {
handleClick()
} finally {
isBatchingUpdates = false
// Then update
}
Copy the code
Before handleClick is executed, React defaults to a setState that should be updated in batches.
When we perform setState inside handleClick, the part of the code that updates the state is first put into a queue for subsequent use. Then proceed with the logic of the update, since triggering setState would certainly trigger the flow of a series of component updates. React, however, immediately interrupts the update process if it finds that states need to be updated in batches.
That is, although we call setState three times in handleClick, we do not complete the component update process for three times, just throw the logic to update the state into a queue. When handleClick completes, the component update process is performed again.
The component update process actually has two distinct branches. One is to trigger the update and complete the entire component update process at once. The other way is to complete all component updates in time segments after triggering the update, so that the user experience is better, which is called task scheduling. If you want to learn more about this section, you can read my previous post.
Of course, this article will also cover some scheduling, which is also included in the component update process. But before we learn about task scheduling, we need to take a look at Fiber, which is the cornerstone of React’s new capabilities.
What is Fiber? What’s the use?
Before we get to Fiber, let’s take a look at why React went to so much trouble refactoring.
In React 15, if a component needed to be updated, we recursively traversed the entire virtual DOM tree to determine what needed to be updated. The downside of this recursive approach is that there is no way to interrupt, and you have to update all the components before stopping. This disadvantage is that if we need to update some large component, the main thread may be blocked for a long time during the update process, causing user interactions, animation updates, and so on to fail to respond in a timely manner.
The React component update process is simply a process of continuously calling functions, which forms a virtual call stack. We can solve this problem if we control the execution of the call stack, break down the entire update task, and put the update task into the browser’s free time as much as possible.
Now it’s time to introduce Fiber. Fiber reimplements the React core algorithm, bringing incremental updates to the killer app. It has the ability to break the entire update task into small tasks and control the execution of these tasks.
These functions are implemented through two core technologies:
- New data structure fiber
- The scheduler
New data structure fiber
In the previous article, we talked about the need to split the update task, so how to control the granularity of the split? The answer is fiber.
We can think of each fiber as a unit of work, and the entire process of performing the update task (not including rendering) is to repeatedly find and run the units of work, thus achieving the function of splitting the task.
The purpose of breaking up the units of work is to allow us to control the stack frames (the contents of the call stack) and execute them anytime, anywhere. This allows us to continue or interrupt work as needed after each unit of work (interruption is at the discretion of the scheduling algorithm).
So what does fiber actually look like? Now let’s take a look.
Fiber actually stores a lot of context information inside of it, and we can think of it as an improved virtual DOM that also corresponds to component instances and DOM elements. Meanwhile, fiber also forms a Fiber tree, but its structure is no longer a tree, but a linked list structure.
Here are some important properties in Fiber:
{...// DOM node in browser context
stateNode: any,
// Form a list structure
return: Fiber | null.child: Fiber | null.sibling: Fiber | null.// Update the correlation
pendingProps: any, / / new props
memoizedProps: any, / / the old props
// Store the first argument in setState
updateQueue: UpdateQueue<any> | null.memoizedState: any, / / the old state
// Schedule related
expirationTime: ExpirationTime, // Task expiration time
// In most cases each fiber has a double fiber
// During the update process, all the operations are done on the replacement. When the rendering is complete,
// Surrogates will replace themselves
alternate: Fiber | null.// Start by simply updating DOM related content
effectTag: SideEffectTag, // Refers to the DOM operations that this node needs to perform
// The following three attributes also form a linked list
nextEffect: Fiber | null.// The next node that needs DOM manipulation
firstEffect: Fiber | null.// The first node that requires DOM manipulation
lastEffect: Fiber | null.// The last node to perform DOM operations on, which can also be used for recovery tasks. }Copy the code
In general, we can think of Fiber as a data structure representation of a unit of work, but it is also an important part of the call stack.
Fiber is not the same concept as Fiber. The former represents the new harmonizer, while the latter represents a Fiber node, which can also be considered an improved virtual DOM.
Introduction to the scheduler
Each time a new update task occurs, the scheduler assigns a priority to the task according to the policy. For example, the update priority of animations will be higher, and the update priority of off-screen elements will be lower.
By this priority we can get a deadline by which the update task must be executed. The higher the priority, the closer the deadline, and vice versa. The cutoff time is used to determine whether the task has expired. If it has expired, the task will be executed immediately.
The scheduler then implements the requestIdleCallback function to perform these updates when the browser is idle.
The implementation of this is slightly more complicated. To put it simply, it is to obtain the end time of each frame through the way of timer. Once we get the end time of each frame we can determine the difference between the current end time and the current end time.
If the end time is not up, it means I can continue with the update task; If the end time has passed, it means that the current frame has run out of time for me to execute the task and must give execution back to the browser, which interrupts the task.
In addition, when the update task (that is, the process of finding and executing a unit of work) starts, if a new update task comes in, the scheduler will make a decision based on the priority of the two tasks. If the new task is a low priority, of course continue with the current task. If the new task has a high priority, the task is interrupted and a new task is started.
That’s how the scheduler works. If you want to learn more, you can read my article on React source code: Scheduler Principles.
summary
Now it’s time to put together the things mentioned in this article, and let’s assume that the update task must trigger scheduling.
When setState is called by an interaction event, a batch update is triggered and the state does not change until the entire interaction event callback has been executed.
After the callback completes, the update task starts and the schedule is triggered. The scheduler prioritizes each of these update tasks and executes them when the browser is idle, except when the task is expired (updates are triggered immediately and no longer wait).
If a new task comes in during the update task, the system determines the priority of the two tasks. If the new task is a high priority, interrupt the old one and start over, otherwise continue.
The last
Reading the source code can be a tedious process, but the benefits can be huge. If you have any questions along the way, please feel free to let me know in the comments section.
Also, writing this series is a time-consuming project, including maintaining code comments, making the article as readable as possible, and finally drawing. If you think the article looks good, please give it a thumbs up.
Finally, feel the content is helpful can pay attention to my public number “front-end really fun”, there will be a lot of good things waiting for you.