preface

We know that React has a dual-cache Fiber tree since version 16, but we don’t know what that means. After pondering for a while, I feel that there is no need to use dual cache fiber tree. A single fiber tree can also meet the needs. We should know that vUE uses a single virtual tree structure.

The function of a dual-cache fiber tree is not only used as a virtual node tree in MVVM framework to implement responsive updates. It is more to serve Concurrent mode.

Next, we will use this article to explain.

Dual cache Fiber tree

First, let’s take a quick look at React’s dual-cache Fiber tree.

When React updates, two Fiber trees exist simultaneously. One is the existing Old Fiber tree, corresponding to the content displayed on the current screen. It can be accessed through the Currrent pointer of the Root fiberRootNode, and is called the Current fiber tree. The other is a new Fiber tree built during the update process, called the workInProgress Fiber Tree.

Diff comparison is to determine whether fiber nodes in the current fiber tree can be reused by the workInProgress fiber Tree during the construction of the workInProgress fiber Tree. Can be reused, means in this update, need to do the component update and DOM node move, update and other operations; Non-reusable means that you need to mount and unmount components and insert and delete dom nodes.

After the update is complete, the Current pointer of fiberRootNode will point to the workInProgress Fiber Tree as the current Fiber tree of the next update.

As for the dual cache technology and dual cache Fiber tree, cason’s work on the React Technology Reveal – Fiber architecture has been explained in detail in the previous article, if you are interested, you can check it out.

React Concurrent

Here’s what the official website says about Concurrent:

Concurrent mode is a new set of features in React that help applications stay responsive and adjust appropriately based on the user’s device performance and network speed.

Concurrent is a new feature provided by React, the most critical of which is to change the original synchronous non-interruptible updates into interruptible asynchronous updates.

Synchronized non-interruptible updates mean that even if higher-priority updates are generated during the update process, the original update will continue to be processed until the higher-priority updates are rendered to the screen.

The asynchronous interruptible update means that in the same case, the original update can be interrupted first, and the update with higher priority will be processed first, and the original interrupted update will be processed after the processing is completed.

With respect to interruptible/non-interruptible updates, we use a demo to illustrate.

function Parent() { const [number, setNumber] = useState(1); const buttonRef = useRef(null); const add = () => { setNumber(number + 1) } const click = () => { buttonRef.current.click() } return ( <div> <button Parent</button> <span>{number}</span> <Child callback={click} /> </div>)} const Child  = (props) => { const [number, setNumber] = useState(1); Const click = () => {setTimeout(() => {// setTimeout() => {setNumber(number + 1); }, 10) setTimeout(() => {// update.function.callback && function.callback (); }, 10); } return (<div> <button onClick={click}> Child + Parent</button> <div className="box"> {Array(50000).fill(number).map(item => (<span>{item}</span>))} </div> </div> ) }Copy the code

In the demo above, we click on the Child button and add 1 to both the Child and Parent’s number. Child’s + 1 starts first, and Parent’s + 1 takes precedence.

Uninterruptible updates:

ReactDOM.render(<Parent />, document.geElementById('app'));
Copy the code

Looking at the example, it’s obvious that the Child’s increment completes before the Parent’s increment. Although the Parent’s increment has a higher priority, it still waits until the Child’s increment is complete.

The internal working process is as follows:

Interruptible update:

ReactDOM.createRoot(document.getElementById('app')).render(<Parent />);
Copy the code

In this example, it is obvious that the Parent + 1 operation is completed first. In Concurrent mode, although the Child’s increment operation starts first, it is interrupted by the Parent’s increment operation and does not continue until the Parent’s increment operation is complete.

The internal working process is as follows:

Asynchronous interruptible updates, in the case of the Fiber Tree, means that the entire process of building the workInProgress Fiber Tree is interruptible. React will stop the workInProgress Fiber tree and start processing higher-priority updates if a higher-priority update is generated during the workInProgress process. Rebuild the workInProgress Fiber Tree. Updates that were interrupted are processed only after higher-priority updates are processed.

The corresponding source code is as follows:

Function renderRootConcurrent(root, lane) {... // workInProgressRootRenderLanes ! If (workInProgressRoot! == root || workInProgressRootRenderLanes ! == lanes) { ... WorkInProgress tree prepareFreshStack(root, lane); . }... }Copy the code
function prepareFreshStack(root, lanes) { ... workInProgressRoot = root; WorkInProgress = createWorkInProgress(root.current, null); . }Copy the code

We add the following console.log to the source code

function renderRootConcurrent(root, lanes) { ... // workInProgressRootRenderLanes ! If (workInProgressRoot! == root || workInProgressRootRenderLanes ! == lanes) { ... The console. The log (' reset workInProgress tree ', workInProgressRootRenderLanes, lanes) / / the original update interrupt, WorkInProgress tree prepareFreshStack(root, Lanes) from scratch; . }... }Copy the code

Running the above example again, we can clearly see how the priority change causes the workInProgress Fiber tree to reset.

At this point, we can understand why React has a dual-cache Fiber tree structure. Current fiber tree: records the status of the last update. WorkInProgree Fiber Tree, a new tree created during the update process, can be reset as the update priority changes.

If you take a single fiber tree, the following would happen for asynchronous interruptible updates:

Surely this is not what we expected.

The last

Of course, React uses a dual-cache Fiber tree, not only for more urgent updates to interrupt renderings that have already started, but also for new features like Suspense, useTransition, useDeferedValue, etc. Due to the length of the article and their own still not fully understand the situation, here is not to do the explanation, after sorting out and then open a new article to introduce.

Because my level is limited, the content in the article may not understand the situation in place or wrong, I hope you can leave a message to point out, 😊. Learn together and make progress together.

Reference documentation

  • React official documents
  • React Technology revealed