preface
The React source reference version is 17.0.3. This is the React source code series ninth, suggest the first look at the source of the students from the first to see, so more coherent, there are source series links below.
Finally, this is the end of version 17, the next one is the future.
The warm-up to prepare
How does React React do this? This post reveals the React update mechanism.
Several scenarios that trigger updates
If a component’s props or state have changed, there are several scenarios that could cause them to change:
- First render (i.e., in the lifecycle)
setState
); - Scheduled task (There are scheduled tasks
setState
); - Event callback (the callback has
setState
); context
Change (Context.Provider
thevalue
);
In our daily development, React updates are triggered mainly by setstates, so let’s explore the React update mechanism from setstates.
setState
after
In the React source series three: When React executes setState, it actually triggers a dispatchAction that creates an update based on the setState input. ScheduleUpdateOnFiber is displayed.
scheduleUpdateOnFiber
Here, the task in the current task queue is judged based on the trigger time of the current setState. If the task time is smaller than currentTime, it is an expired task and needs to be executed immediately in the next update.
markStarvedLanesAsExpired(root, currentTime);
Copy the code
Getting the priority of the current setState task is a more complicated piece of code judgment, which I did not delve into
var nextLanes = getNextLanes(root, lanes);
var newCallbackPriority = returnNextLanesPriority();
Copy the code
After determining the priority, create a callback task based on the priority and store it in root.callbackNode. The following are mainly based on the priority to distinguish the synchronous task, asynchronous task or batch update task, and the priority of execution.
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(ImmediatePriority$1, performSyncWorkOnRoot.bind(null, root));
} else {
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
}
Copy the code
In both scheduleSyncCallback and scheduleCallback, the unstable_scheduleCallback function is called at the end. In this function, the task is created based on the expiration time calculated based on the priority
switch (priorityLevel) {
case ImmediatePriority:
timeout = -1;
break;
case UserBlockingPriority:
timeout = 250;
break;
case IdlePriority:
timeout = 1073741830;
break;
case LowPriority:
timeout = 10000;
break;
case NormalPriority:
default:
timeout = 5000;
break;
}
var expirationTime = startTime + timeout;
Copy the code
A task with a long expiration time has a low priority and can be executed later. A task with a high priority will have a smaller expiration time than the current one, so it will be an expired task directly. React will execute the task immediately next time.
Once the task is created, when will it be executed? When the task is created, React will create a macro task via MessageChannel and immediately port2. PostMessage will tell Port1 to execute performWorkUntilDeadline. PerformWorkUntilDeadline executes the passed callback function.
We temporarily don’t consider the priority and scheduling the timing problem, put the center of gravity in it will perform in the callback function, we can notice that in the three tasks, ultimately to the callback is performSyncWorkOnRoot. Bind (null, root), that is to say, no matter what time, Finally, performSyncWorkOnRoot is executed, so we know where to go next: performSyncWorkOnRoot.
var channel = new MessageChannel(); var port = channel.port2; channel.port1.onmessage = performWorkUntilDeadline; // Implement asynchronous scheduling updates based on MessageChannel, If MessageChannel is not supported setTimeout requestHostCallback = function (callback) {scheduledHostCallback = callback; if (! isMessageLoopRunning) { isMessageLoopRunning = true; port.postMessage(null); }};Copy the code
performSyncWorkOnRoot
createrootFiber
if (workInProgressRoot ! == root || workInProgressRootRenderLanes ! // Initialize workInProgressRoot, workInProgress, mount, update copy prepareFreshStack(root, workInProgress); startWorkOnPendingInteractions(root, lanes); }Copy the code
In prepareFreshStack, if currentFiber tree is present, React will determine that it is the update phase and it will copy the rootFiber from Curret as workInprogress.
With rootFiber, the next step is to recurse through the child nodes of this rootFiber, find out the difference between currentFiber tree and currentFiber tree, and then update, and the render stage of beginWork, completeWork and commitWork is the same.
beginWork
The initial render phase and the initial render phase execute a function, but will differ in some logical decisions.
React: Current === null is an important reference when deciding whether to initialize the render phase or update phase.
React has the following judgment in the beginWork stage of rendering
if (oldProps ! == newProps || hasContextChanged() || (workInProgress.type ! == current.type )) { didReceiveUpdate = true; }Copy the code
We can note that workinprogress.type! == current. Type == current. Type == current. If inconsistent didReceiveUpdate = true; . Now look at the code below when didReceiveUpdate = true; What would you do
if (current ! == null && ! didReceiveUpdate) { bailoutHooks(current, workInProgress, renderLanes); / / clone current fiber and return return bailoutOnAlreadyFinishedWork (current, workInProgress renderLanes); } workInProgress.flags |= PerformedWork; reconcileChildren(current, workInProgress, nextChildren, renderLanes);Copy the code
This code tells us the answer when current! == null && ! With didReceiveUpdate, the value of current is copied directly and returned, otherwise, when didReceiveUpdate = true; When this judgment will not enter, it will go down again
Suppose there is a
After the React component is executed, it will start to recursively iterate over the React element of the function component return. This is the React diff algorithm.
completeWork
In the completeWork phase, the update phase is mainly for the props of new and old nodes to diff, in the previous article also introduced. AppendAllChildren is primarily implemented during the initial render phase.
AppendAllChildren will be implemented if there is a new React element added during the update phase, adding the new child node from the fiber node to the DOM.
commitWork
In the mutation phase of commitWork, that is, when the DOM is rendered, the changes diff out of the completeWork are updated into the DOM, and the page is actually rendered.
Add and delete the DOM, not to mention, are in accordance with the normal rendering process. Let’s look at DOM updates
function updateDOMProperties(domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag) { for (var i = 0; i < updatePayload.length; i += 2) { var propKey = updatePayload[i]; var propValue = updatePayload[i + 1]; if (propKey === STYLE) { setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { setTextContent(domElement, propValue); } else { setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); }}}Copy the code
React props to update the DOM according to diff props from completeWork. Differentiate props from style, dangerouslySetInnerHTML, Chidren and others.
style
: is for DOMstyle
;dangerouslySetInnerHTML
:React
The API for writing DOM nodes (useful in rich text, use with caution) is actually used in the source codeinnerHtml
;children
:children
Default to text, rendering directly;- other
prop
: Mainly throughsetAttribute
andremoveAttribute
To deal with;
conclusion
To summarize the updates caused by setState:
- First of all by
dispatchAction
To build aupdate
Stored in the correspondingfiber
Node, and then start scheduling updates; - When scheduling updates, it determines whether there is an update task in the current application, and then determines this time
setState
Task priority determines the update time; - in
setState
When the task starts executing, it will be the same as the initial renderbeginWork
.completeWork
.commitWork
; - in
beginWork
.completeWork
throughdiff
Determine the element to be updated; - in
commitWork
Lt.diff
The updates are applied to the DOM and rendered to the page;
So far, our series of articles have analyzed React fiber, initial rendering, hook, composite event, DIff algorithm, and update mechanism. There must be some incompleteness and errors. Moreover, the author mainly analyzed function components and client rendering, so I can only say that I have a general understanding of React operation mechanism. I hope you can also see the harvest.
After reading this article, we can understand the following questions:
React
What is the update mechanism?
Article series arrangement:
- React Fiber;
- React source series 2: React rendering mechanism;
- React source series 3: hooks useState, useReducer;
- React code series 4: hooks useEffect;
- React code Series 5: Hooks useCallback, useMemo;
- React source Series 6: Hooks useContext;
- React source series 7: React synthesis events;
- React source series eight: React diff algorithm;
- React source series 9: React update mechanism;
- React source series 10: Concurrent Mode;
Reference:
React official documents;
Making;