We learned earlier:
- The function that starts the Render phase is renderRootSync
- The commit phase starts with the commitRoot function
Note: The render phase is not a function called by render. It refers to the render phase, the execution after the render function is called.
Here’s what happens in the Render phase. We knew earlier that the Render phase was implementing interruptible recursion (time slicing) to improve performance. There are two parts of recursion and recursion:
- The method for the recursion phase is beginWork
- The method for phase execution is completeWork
The next step is to study them and see what ying they are doing.
First of all, the code in our app.js file is as follows:
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Copy the code
mount
About the process
The Fiber dual-cache architecture does not have a current Fiber tree when mounted, but a current Fiber tree when updated.
So beginWork and completeWork are different when they mount and when they update.
Here’s what you do when you first enter beginWork: Look at this file in your project: / react_origin_code_debugger/react/build/node_modules/react – dom/CJS/react – dom. Development. The inside of the js beginWork code is as follows:
function beginWork(current, workInProgress, renderLanes) { // workInProgressRoot: FiberNode // current: FiberNode // First node to beginWork, Current. Tag === 3 // current. Tag === 3 corresponds to HostRoot-- indicates the current corresponding root node // Current === null {if (workInProgress._debugNeedsRemount && current ! == null) { return remountFiber( current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes)); } } if (current ! == null) { var oldProps = current.memoizedProps; var newProps = workInProgress.pendingProps; if (oldProps ! == newProps || hasContextChanged() || ( workInProgress.type ! == current.type )) { didReceiveUpdate = true; } else { var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes); if (! hasScheduledUpdateOrContext && (workInProgress.flags & DidCapture) === NoFlags) { didReceiveUpdate = false; return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes); } if ((current.flags & ForceUpdateForLegacySuspense) ! == NoFlags) { didReceiveUpdate = true; } else { didReceiveUpdate = false; } } } else { didReceiveUpdate = false; } workInProgress.lanes = NoLanes; // Switch (workinprogress.tag) {... } { { throw Error( "Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in React. Please file an issue." ); }}}Copy the code
If the beginWork node is entered for the first time, the current. Tag value is 3. Current. Tag: 3 corresponds to HostRoot– represents the current corresponding root node (FiberNode)
Said the meaning of the tag can see: in the project here/react_origin_code_debugger/react/packages/react – the reconciler/SRC/ReactWorkTags. Js
If there is no local can be found here: github.com/sunkuangdon… You can clone it and run it locally.
When we enter beginWork again, current === null, as can be seen in the picture below
As we explained earlier in the Dual-cache architecture of the Fiber architecture, for the first screen rendering, only the current Fiber exists in the root node of the current application, and only the workInProgress Fiber exists in the other nodes.
In the figure below, you can see that the current workInProgress Fiber is App:
WorkInProgress Fiber was the same as Current Fiber when I first entered it. If you don’t believe me, you can hit the debugger and have a look again. It won’t be shown here.
You can see that the previous Fiber dual cache architecture is correct analysis. Ha-ha-ha-ha, first kua wave ~
The third time you enter beginWork, workInProgress Fiber is div:
When entering the beginWork for the fourth time, the workInProgress Fiber should be header, and then proceed according to the app.js hierarchy.
When you go to IMG you don’t have children, you have siblings P and A. Starting from the IMG node, you jump out of the beginWork function and execute the completeWork function.
The completeWork function’s workInProgress Fiber is img, so the img sibling node is searched.
Img has a sibling node P and will then enter the beginWork of node P
The p node has three child nodes. Enter beginWork again and you will see the text node of the child node: Edit
The text node Edit has no children and enters the completeWork function. The current node is the Edit text node:
And then you go to the code node’s beginWork function, and then you go to the code node and you see instead of beginWork, you have the completeWork function. But code clearly has a child node: SRC/app.js.
This is because React optimizes nodes that have only one text child node. In this case, the text node will not regenerate into its own Fiber node.
Next, after code completes completeWork, it looks for the sibling node that generated code, in this case: and save to reload. Text node.
In the execution, it must be and save to reload. The completeWork of the text node will enter the completeWork of the parent node, which is the completeWork of the P node.
When p’s completeWork finishes, the beginWork of p’s sibling node A enters:
The only child node of the A node is the Learn React text node, so this text node does not have its own Fiber node. So I’m going to go to the completeWork of the A node, and then I’m going to go up a little bit to the completeWork of the header node… Finally, enter the FiberNode node, the current root node.
At this point the Render phase is complete and the commit phase is entered.
The specific work of beginWork
So that’s the general process, but what does the beginWork function actually do?
We know that the first entry to beginWork is the root Fiber node of the current application, and its tag === 3. For the analysis, we use the div Fiber node.
First of all, the beginWork will enter different cases according to different tags, and the above cases are ignored. Here they are taken out separately for analysis:
switch (workInProgress.tag) { case IndeterminateComponent: { return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes); } case LazyComponent: { var elementType = workInProgress.elementType; return mountLazyComponent(current, workInProgress, elementType, renderLanes); } case FunctionComponent: { var Component = workInProgress.type; var unresolvedProps = workInProgress.pendingProps; var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes); } case ClassComponent: { var _Component = workInProgress.type; var _unresolvedProps = workInProgress.pendingProps; var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps); return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes); } case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostComponent: return updateHostComponent$1(current, workInProgress, renderLanes); case HostText: return updateHostText$1(current, workInProgress); case SuspenseComponent: return updateSuspenseComponent(current, workInProgress, renderLanes); case HostPortal: return updatePortalComponent(current, workInProgress, renderLanes); case ForwardRef: { var type = workInProgress.type; var _unresolvedProps2 = workInProgress.pendingProps; var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2); return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes); } case Fragment: return updateFragment(current, workInProgress, renderLanes); case Mode: return updateMode(current, workInProgress, renderLanes); case Profiler: return updateProfiler(current, workInProgress, renderLanes); case ContextProvider: return updateContextProvider(current, workInProgress, renderLanes); case ContextConsumer: return updateContextConsumer(current, workInProgress, renderLanes); case MemoComponent: { var _type2 = workInProgress.type; var _unresolvedProps3 = workInProgress.pendingProps; var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3); { if (workInProgress.type ! == workInProgress.elementType) { var outerPropTypes = _type2.propTypes; if (outerPropTypes) { checkPropTypes(outerPropTypes, _resolvedProps3, 'prop', getComponentNameFromType(_type2)); } } } _resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3); return updateMemoComponent(current, workInProgress, _type2, _resolvedProps3, renderLanes); } case SimpleMemoComponent: { return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes); } case IncompleteClassComponent: { var _Component2 = workInProgress.type; var _unresolvedProps4 = workInProgress.pendingProps; var _resolvedProps4 = workInProgress.elementType === _Component2 ? _unresolvedProps4 : resolveDefaultProps(_Component2, _unresolvedProps4); return mountIncompleteClassComponent(current, workInProgress, _Component2, _resolvedProps4, renderLanes); } case SuspenseListComponent: { return updateSuspenseListComponent(current, workInProgress, renderLanes); } case ScopeComponent: { break; } case OffscreenComponent: { return updateOffscreenComponent(current, workInProgress, renderLanes); } case LegacyHiddenComponent: { return updateLegacyHiddenComponent(current, workInProgress, renderLanes); } case CacheComponent: { { return updateCacheComponent(current, workInProgress, renderLanes); }}}Copy the code
Div is a HostComponent, so it goes to the: updateHostComponent$1 function
The updateHostComponent$1 function copies a number of variables first. Var isDirectTextChild = shouldSetTextContent(type, nextProps) Whether the current Fiber node has a single text child node to optimize (no Fiber nodes will be created for text child nodes).
The reconcileChildren method is next brought into reconcileChildren. Before executing this method, the current workinProgress.child === NULL, So the reconcileChildren method creates sub-Fiber nodes for the current Fiber node.
The name of the function already tells us that the current implementation phase is the Render phase, which is…. running in the Reconciler
Into the reconcileChildren, as you can see in the following figure, enter the mountChildFibers or reconcileChildFibers by checking whether Current is equal to NULL
What’s the difference between these two methods?
We enter in the project: / react_origin_code_debugger/react/packages/react – the reconciler/SRC/ReactChildFiber. Old. Js file. As you can see in the figure below, both functions are actually the return values of the ChildReconciler function calls, but ChildReconciler takes different arguments
What the ChildReconciler parameter means: Whether to track side effects.
- Using the deleteChild function as an example, we can see that if it is false, it does not trace the side effects and returns directly.
- If side effects are tracked, childToDelete is appended to deletions
The Render phase marks the DOM operations to be performed, and the DOM operations to be performed are the Commit phase. The following is a partial markup of the DOM operation
Such as:
- Placement: The DOM node corresponding to the Fiber node needs to be inserted into the page.
- Deletion: The DOM node corresponding to the Fiber node needs to be deleted.
Why is it marked in binary?
If you have a DOM node corresponding to a Fiber node that first needs to be inserted into the page and then needs to Update its properties, it needs to have both Placement and Update tags. | = operator operation can be used to make a variable has two operations at the same time.
How to insert the DOM into a page will be covered in the next article.
Next up: the reconcileChildren function reconcileChildFibers method.
In reconcileChildFibers, he identifies the current type of newChild and treats it differently according to the reconcileChildFibers.
conclusion
When a Fiber node enters the beginWork, its ultimate goal is to create the first child of the current Fiber node.
- First, the current Fiber node type is determined to enter the logic of the different Update.
- Then, in the Update logic, he will determine whether the current Fiber exists in the current workInProgress Fiber to determine whether to mark the EffectTag. (reconcileChildren).
- The reconcileChildFibers logic then reconcils the reconcileChildFibers, which determines the type of child in the current Fiber to perform the different creation operations. Eventually a child Fiber node is created
Some of the key functions executed are in parentheses above. Note that every time the beginWork method is executed, only one Fiber node is created, even if there is an Array type in it.