React Fiber is a new feature that most people know about. It’s a new feature called React Fiber, a new data structure, and a scheduler-on-update mechanism.

But if you’re asked:

  1. React Fiber, why not vue Fiber?
  2. React Fiber allows you to recover from a broken point of view when traversing a virtual DOM tree.

This article will address these two issues from the point of view of the responsive design of the two frameworks. It will not involve obscure source code, and it will be easy to read whether you have used React or not.

What is reactive

Whether you use React or Vue, the term “responsive update” is familiar.

Reactive, which intuitively means that the view updates automatically. It might seem like a natural thing to do if you started working with the front end and started working with frameworks, but before “responsive frameworks” came along, it was a hassle.

Here I will create a time display using native JS, React, and Vue.

  1. Native js:

To change something on the screen, you must first find the DOM (document.getelementById) and then modify the DOM (clockdom.innerText).

<div id="root">
    <div id="greet"></div>
    <div id="clock"></div>
</div>
<script>
    const clockDom = document.getElementById('clock');
    const greetDom = document.getElementById('greet');
    setInterval(() = > {
        clockDom.innerText = 'Now it is:${Util.getTime()}`
        greetDom.innerText = Util.getGreet()
    }, 1000);
</script>
Copy the code

With a responsive framework, things get easier

  1. React:

To make changes to the content, just call setState to modify the data and the page will be re-rendered.

<body>
    <div id="root"></div>
    <script type="text/babel">
        function Clock() {
            const [time, setTime] = React.useState()
            const [greet, setGreet] = React.useState()
            setInterval(() = > {
                setTime(Util.getTime())
                setGreet(Util.getGreet())
            }, 1000);
            return ( 
                <div>
                    <div>{greet}</div>
                    <div>Now: {time}</div>
                </div>
            )
        }
        ReactDOM.render(<Clock/>.document.getElementById('root'))
    </script>
</body>
Copy the code
  1. Vue:

We don’t have to worry about the DOM either. When modifying the data, just go to this.state= XXX and the page will display the latest data.

<body>
    <div id="root">
        <div>{{greet}}</div>
        <div>Now: {{time}}</div>
    </div>
    <script>
        const Clock = Vue.createApp({
            data(){
                return{
                    time:' '.greet:' '}},mounted(){
                setInterval(() = > {
                    this.time = Util.getTime();
                    this.greet = Util.getGreet();
                }, 1000);
            }
        })
        Clock.mount('#root')
    </script>
</body>
Copy the code

React and Vue response principles

React calls setState while Vue modifies variables directly. It may seem that the two frameworks are used differently, but that’s where the reactive principle lies.

In React, component states cannot be changed. SetState does not change variables in the original memory block, but creates a new memory block. Vue, on the other hand, is the raw memory that directly modifies the saved state.

The word “immutable” often appears in articles about React, which translates to immutable.

In React, the setState method is used to re-render the component from the top down. This means that the component and its children are all rendered. Vue uses Object.defineProperty (vue@3 migrated to Proxy) to hijack setters and getters for data. That is, vue knows exactly which part of the view template uses the data and tells the view when the data is modified, You need to re-render.

So when a piece of data changes, react component rendering is performance-intensive — the parent component’s state is updated, and all the child components have to render along with it. It’s not as granular as Vue is to the current component.

To prove it, I wrote a demo using React and Vue respectively. The function is very simple: the parent component nested child components. Clicking the button of the parent component will change the state of the parent component, and clicking the button of the child component will change the state of the child component.

Instead of using the more popular React function component, vue also uses the uncommon Render method for better comparison and visualization of the render phase:

class Father extends React.Component{
    state = {
        fatherState:'Father-original state'
    }
    changeState = () = > {
        console.log('-----change Father state-----')
        this.setState({fatherState:'Father-new state'})}render(){
        console.log('Father:render')
        return ( 
            <div>
                <h2>{this.state.fatherState}</h2>
                <button onClick={this.changeState}>change Father state</button>
                <hr/>
                <Child/>
            </div>)}}class Child extends React.Component{
    state = {
            childState:'Child-original state'
    }
    changeState = () = > {
        console.log('-----change Child state-----')
        this.setState({childState:'Child-new state'})}render(){
        console.log('child:render')
        return ( 
            <div>
                <h3>{this.state.childState}</h3>
                <button onClick={this.changeState}>change Child state</button>
            </div>
        )
    }
}
ReactDOM.render(<Father/>.document.getElementById('root'))
Copy the code

React: Rerender Father:render Child: Render Father:render Child :render (Try the online demo here)

 const Father = Vue.createApp({
    data() {
        return {
            fatherState:'Father-original state',}},methods: {changeState:function(){
            console.log('-----change Father state-----')
            this.fatherState = 'Father-new state'}},render(){
        console.log('Father:render')
        return Vue.h('div',{},[
            Vue.h('h2'.this.fatherState),
            Vue.h('button', {onClick:this.changeState},'change Father state'),
            Vue.h('hr'),
            Vue.h(Vue.resolveComponent('child'))
        ])
    }
})
Father.component('child', {data() {
        return {
            childState:'Child-original state'}},methods: {changeState:function(){
            console.log('-----change Child state-----')
            this.childState = 'Child-new state'}},render(){
        console.log('child:render')
        return Vue.h('div',{},[
            Vue.h('h3'.this.childState),
            Vue.h('button', {onClick:this.changeState},'change Child state'),

        ])
    }
})
Father.mount('#root')
Copy the code

As shown above with vue, the component rerenders only the smallest particles regardless of which state it is changing: click change Father State to print Father:render only, not child: Render.

(Try the online demo here)

The influence of different responsivity principle

First of all, it should be emphasized that the “render”, “render” and “update” mentioned above do not mean that the browser actually renders the view. Instead, the framework invokes its own render method at the javascript level to generate a common object that holds the properties of the real DOM, also known as the virtual DOM. This article will distinguish between component rendering and page rendering.

Each view update process looks like this:

  1. The component renders a new virtual DOM tree.
  2. Compare the old and new virtual DOM trees to find the changing parts; (Also known as the Diff algorithm)
  3. Create real DOM for the parts that really change, mount them to the document, and rerender the page;

The React component renders a larger virtual DOM tree in the first step when data is updated because react and Vue are implemented in different responsive ways.

What is the fiber

This is all just to clarify why react Fiber is needed: When the data is updated, React generates a larger virtual DOM tree, which puts a lot of pressure on the diff in step 2 — we want to find the part that really changes, which takes longer. Js takes up the main thread for comparison, rendering thread can’t do other work, user interaction can’t respond, hence react Fiber.

React Fiber doesn’t shorten the comparison time, but it does allow diff to be broken up into small chunks because of the ability to “save work progress.” Js will compare a portion of the virtual DOM, then give the main thread to the browser to do other work, and then continue to compare and repeat until the final comparison is complete and the view is updated once.

Fiber is a new data structure

As mentioned above, react Fiber enables the diff phase to save work progress. This section explains why.

To find the part where the state changes, we have to go through all the nodes.

In the old architecture, nodes were organized as a tree: each node had multiple Pointers to its children. The easiest way to find the changing parts of two trees is depth-first traversal, as follows:

  1. All children of the node are traversed, starting from the root node.
  2. A node is considered to have completed traversal only when all its children have completed traversal.

If you’ve systematically studied data structures, you should quickly realize that this is just a follow-up to depth-first traversal. According to this rule, the order in which the nodes complete the traversal is marked on the graph.

One feature of this traversal is that it must be done all at once. If the traversal is interrupted, we can keep the index of the node in progress. The next time we continue, we can indeed continue traversing all the children below that node, but there is no way to find its parent — because each node only points to its children. There is no way to recover the breakpoint, only to start again.

Take this tree for example:

An interruption occurred when traversing to node 2. We saved the index of node 2 and traversed the nodes 3 and 4 below it in the next recovery, but could not retrieve nodes 5, 6, 7 and 8.

In the new architecture, each node has three Pointers: one to the first child node, the next sibling node, and the parent node. This data structure is fiber, and its traversal rules are as follows:

  1. Starting with the root node, the child node is traversed, the sibling node is traversed, and if both are traversed, the parent node is returned.
  2. A node is considered to have completed traversal only when all its children have completed traversal.

According to this rule, the order of completion of node traversal is also marked in the graph. A comparison with a tree structure shows that although the data structure is different, the nodes’ traversal starts and finishes in exactly the same order. The difference is that when traversal breaks, breakpoints can be recovered as long as the index of the current node is preserved — because each node retains the index of its parent.

It is also interrupted when traversing node 2, and the fiber structure enables all remaining nodes to still be traversed.

This is why react Fiber rendering can be interrupted. Trees and Fiber look similar, but essentially, one is a tree and the other is a linked list.

Fiber is fibers

This data structure is called fiber because the translation of fiber is fiber, and it is considered a form of implementation of coroutines. Coroutines are smaller scheduling units than threads: they can be opened and paused by programmers. Specifically, React Fiber is a “progress bar” for component rendering controlled through the requestIdleCallback API.

RequesetIdleCallback is a callback that belongs to a macro task, just like setTimeout. Unlike setTimeout, which is controlled by the callback time we pass in, requesetIdleCallback is controlled by the refresh rate of the screen. This article will not go into the details of this section, just know that it is called every 16ms, its callback function can get the time of execution, each 16ms in addition to the requesetIdleCallback callback, so the available time is uncertain, but once the time is up, I’m going to stop traversing the node.

The usage method is as follows:

const workLoop = (deadLine) = > {
    let shouldYield = false;// Whether the thread should be relinquished
    while(! shouldYield){console.log('working')
        // iterate over nodes, etc
        shouldYield = deadLine.timeRemaining()<1;
    }
    requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop);
Copy the code

The requestIdleCallback callback checks how much time it has left to use by passing in the deadline.timeremaining () argument. The demo above is also pseudocode for how React Fiber works.

However, due to poor compatibility and the infrequency with which the callback function is called, React actually uses a polyfill(its own API) instead of requestIdleCallback.

React Fiber is an update mechanism introduced by React 16 that uses linked lists instead of trees to connect the virtual DOM, allowing component updates to be interrupted and restored. It shards the component rendering work, and at that point actively cedes the main render thread.

React Fiber brings changes

Let’s start with a comparison that’s been circulating in the community using React 15 and 16. This is a triangle with varying width. The number in the middle of each small circle will change over time. In addition, hovering the mouse will change the color of the small dots.

(React15-Stack online address | react16-fiber)

Practical operation, we can find two characteristics:

  1. With the new architecture, animations are smoother and width changes don’t get stuck;
  2. With the new architecture, users respond faster and the color changes faster when they hover over the mouse.

Pause for a moment. Fiber is responsible for both — user responsiveness is understandable, but does react Fiber speed up rendering?

The root cause of smooth animation must be getting more frames per second. However, when we used React Fiber, we did not reduce the total time required to update.

To make it easier to understand, I drew a picture of the state of the refresh:

Here is how long it takes to get each frame when using the old React architecture. Here is how long it takes to get each frame when using Fiber architecture. Since component rendering is fragmented, the time to complete a frame update is delayed.

React will ensure that every update is complete.

But the animation does flow. Why?

I took the project’s repository down and looked at its animation implementation: the component animation is not achieved by directly modifying width, but by using the Transform: Scale property with the 3D transform. If you’ve heard of hardware acceleration, you probably know why: setting up the page to re-render doesn’t rely on the main render thread shown above, but is done directly on the GPU. In other words, the render main thread only needs to ensure that it has some time slice to respond to the user interaction.

-<SierpinskiTriangle x={0} y={0} s={1000}>
+<SierpinskiTriangle x={0} y={0} s={1000*t}>
    {this.state.seconds}
</SierpinskiTriangle>
Copy the code

Modify line 152 of the project code and change the graphics to width. Even with React Fiber, the animation gets pretty sluggish, so the smoothness here is mostly due to the CSS animation. (Use caution if you don’t have much memory, your browser will freeze)

React not as good as Vue?

We now know that react Fiber is a way to compensate for the “mindless” refresh of updates that aren’t precise enough. Does react perform worse?

And it isn’t. Which is better is a very controversial topic, and I will not comment on it here. Accurate updates come at a cost to VUE. On the one hand, a “monitor” needs to be configured for each component to manage the dependency collection of views and release notifications when data is updated, which also has a performance cost. Vue, on the other hand, is able to implement dependency collection thanks to its template syntax, which allows for static compilation in a way that React, which uses the more flexible JSX syntax, cannot.

Before react Fiber came along, React also provided PureComponent, shouldComponentUpdate, useMemo,useCallback methods to declare which subcomponents don’t need to be updated.

conclusion

Returning to the first few questions, the answers are not hard to find in the text:

  1. React Fiber was used to slice up the component rendering work due to its inherent deficiency of not being able to update accurately. Vue is based on data hijacking, and the update granularity is very small, so there is no such pressure;
  2. React Fiber is a data structure that allows a node to trace back to its parent node. As long as the interrupted node index is retained, the previous work progress can be resumed.

If this article is helpful to you, give me a thumbs up. It means a lot to me

(Click follow better! 3 > <)