Now that we’re talking about Fiber, the first thing we need to know is why this thing called Fiber was created, and why it happened.

React before Fiber

Before React 16 introduced Fiber, React used a recursive virtual DOM tree to find the nodes that needed to be changed.

We can do a simple simulation with a piece of code.

const element = (
    <div id='A1'>
        <div id='B1' className='B11'>
            <div id='C1'></div>
            <div id='C11'></div>
        </div>
        <div id='B2' className='B11'>
            <div id='C2'></div>
        </div>
    </div>
);

function render(element, parentDom) {
    const dom = document.createElement(element.type);

    Object.keys(element.props)
        .filter((ele) = >ele ! = ="children")
        .forEach((item) = > {
            dom[item] = element.props[item];
        });

    if (Array.isArray(element.props.children)) {
        element.props.children.forEach((item) = > render(item, dom));
    } else if (
        element.props.children &&
        Object.keys(element.props.children).length ! = =0
    ) {
        render(element.props.children, dom);
    }

    parentDom.appendChild(dom);
}

render(element, document.getElementById("root"));
Copy the code

Because of the recursive approach, the execution stack gets deeper and deeper, and there is an uninterruptible problem. As the project gets bigger and bigger, the problem gets worse and worse, the recursion gets deeper and deeper, and it gets really stuck.

For example, if you have 100 components and it takes 1 second to render each component, it takes 100 seconds to render all of them. If the user enters a piece of content in those 100s, it will only be displayed after 100s, giving the user the impression of being stuck.

Prepend content

Before we implement Fiber, we need to learn a few things.

Screen refresh rate

Most devices currently have a refresh rate of 60 frames per second, which means the overall page looks smooth at 60 frames per second, below which users feel stuck.

frame

Since the number of frames drawn per second is 60, it is obvious that the time per frame is about 16.6ms (1s/60).

What needs to be done for each frame is as follows:

  1. Input events need to be handled first so that the user can get the earliest feedback.
  2. The next step is to process the timer. You need to check whether the timer is out of time and perform the corresponding callback.
  3. Next process Begin Frame, that is, the event of each Frame, including window.resize, Scroll, media Query change, etc.
  4. Next, the requestAnimationFrame (rAF) is executed, that is, before each drawing, the rAF callback is performed;
  5. This is followed by Layout operations, including calculating the Layout and updating the Layout, that is, how the element should be styled and how it should be displayed on the page.
  6. The browser fills in the content for each element in the tree with information such as the size and position of each node.
  7. At this point, the above six phases have been completed. The next phase is idle, where you can execute the tasks registered in requestIdleCallback.

It is important to note that every frame is not 100 percent idle, and if a task takes too long, the browser will delay rendering.

Concept of Fiber

Fiber can be understood as two concepts, the first is a data structure and the second is an execution unit.

The data structure

React Fiber is implemented with a linked list. Each node is a Fiber, and each Fiber includes child, Sibling, return and other attributes.

Child: first child sibling: return: parent

As shown in the figure below:

Specific Fiber information can be viewed in the following ways

Right-click the corresponding DOM element store as global variable and the following appears

Enter temp1.__reactInternalInstance$3qy8wj4jygh (temp1. Ok, there are intelligent hints behind)

Tag: the type of the current Fiber node stateNode: only want the real of the current node DOM Return: the parent of the current node Sibling: the sibling of the current node Child: the first child of the current node effectTag: the side type of the current node

Execution unit

Fiber can be understood as an execution unit. Regardless of the structure of the execution unit, consider it as an execution unit. Every time a unit is executed, React checks to see if there is any time left.

The following figure shows the process

  1. React requests scheduling from the browser
  2. Give React control of the browser’s free time
  3. React determines if there are any unexecuted tasks and if there is any spare time, and executes them if both, otherwise it gives control back to the browser

API

requestAnimationFrame

A browser-provided API for drawing animations. It requires the browser to call the specified callback function to update the animation before the next frame

Look at the following examples

<body>
    <div
        id="div"
        style="width: 0; height: 50px; background-color: #40a9ff"
    ></div>
    <button id="button">start</button>
</body>
<script>
    let button = document.getElementById("button");
    let div = document.getElementById("div");

    let start = 0;
    let intervalTime = [];

    const progress = () = > {
        div.style.width = div.offsetWidth + 1 + "px";
        div.innerHTML = div.offsetWidth + "%";
        if (div.offsetWidth < 100) {
            let current = Date.now();
            intervalTime.push(current - start);
            start = current;
            requestAnimationFrame(progress);
        } else {
            console.log(intervalTime); // Print time interval}}; button.onclick =() = > {
        div.style.width = 0;
        let currrent = Date.now();
        start = currrent;
        requestAnimationFrame(progress);
    };
</script>
Copy the code

requestIdleCallback

RequestIdleCallback enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses.

Refer to MDN for details

The specific execution process is as follows

The callback in requestIdleCallback(callback) has two attributes

TimeRemaining: How much idle time is left to execute time-consuming tasks didTimeout: Determines whether the current callback function was executed

const works = [
    () = > {
        console.log("Start");
        console.log("End of the A1");
    },
    () = > {
        console.log(B1 "start");
        console.log("B1 end");
    },
    () = > {
        console.log(C1 "start");
        console.log("End of C1");
    },
    () = > {
        console.log("C2 start");
        console.log("C2 end");
    },
    () = > {
        console.log(B2 "start");
        console.log("End of B2"); },]; requestIdleCallback(woorkLoop);function woorkLoop(deadline) {
    console.log("Time remaining in this frame :", deadline.timeRemaining());

    // If there is time left and there are unfinished tasks
    while (deadline.timeRemaining() > 0 && works.length > 0) { performUnitOfWork(); }}function performUnitOfWork() {
    let work = works.shift(); // Fetch the first task and execute it
    work();
}
Copy the code

Suppose one of the intermediate tasks takes longer than one idle frame.

const works = [
    () = > {
        console.log("Start");
        sleep(20);
        console.log("End of the A1");
    },
    () = > {
        console.log(B1 "start");
        sleep(20);
        console.log("B1 end");
    },
    () = > {
        console.log(C1 "start");
        sleep(20);
        console.log("End of C1");
    },
    () = > {
        console.log("C2 start");
        sleep(20);
        console.log("C2 end");
    },
    () = > {
        console.log(B2 "start");
        sleep(20);
        console.log("End of B2"); },]; requestIdleCallback(woorkLoop);function woorkLoop(deadline) {
    console.log("Time remaining in this frame :", deadline.timeRemaining());

    // If there is time left and there are unfinished tasks
    while (deadline.timeRemaining() > 0 && works.length > 0) {
        performUnitOfWork();
    }

    // Time has run out and there are unfinished tasks
    if (works.length > 0) {
        console.log(
            ` only${deadline.timeRemaining()}, this frame time has been used up, please wait for the next schedule '
        );
        // Reschedule the requestrequestIdleCallback(woorkLoop); }}function performUnitOfWork() {
    let work = works.shift(); // Fetch the first task and execute it
    work();
}

// Simulate the waiting time
function sleep(duration) {
    let start = Date.now();
    while (start + duration > Date.now()) {}
}
Copy the code

It can be found that tasks are executed in multiple frames, but there is a problem that C1, C2 and B2 are executed together, and the idle time is obviously longer than the normal time of one frame.

This is because browsers that are idle for long periods of time lengthen the execution time of requestIdleCallback to a maximum of 50ms.

You can verify this using requestAnimationFrame.

requestAnimationFrame(progress);

function progress() {
    console.log("progress");
    requestAnimationFrame(progress);
}
Copy the code

MessageChannel

We mentioned the requestIdleCallback method used to request browser scheduling, but there is a problem with this method, we can look at the compatibility of this API.

You can see that some browsers don’t support this method, so React emulates a requestIdleCallback based on MessageChannel.

A brief introduction to the usage, do not do detailed explanation.

var channel = new MessageChannel();
var port1 = channel.port1;
var port2 = channel.port2;
port1.onmessage = function (event) {
    console.log("Port1 received data from port2:" + event.data);
};
port2.onmessage = function (event) {
    console.log("Port2 received data from port1:" + event.data);
};

port1.postMessage("Send to port2");
port2.postMessage("Send to port1");
Copy the code

MessageChannel emulates requestIdleCallback by calling requestAnimationFrame, as follows.

let channel = new MessageChannel();
let activeTime = 1000 / 16; // Time per frame
let deadLineTime; // One frame deadline
let pendingCallback; 
let timeRemaining = () = > deadLineTime - performance.now(); // The remaining time

channel.port2.onmessage = () = > {
    let currentTime = performance.now();
    // If the frame expiration time is less than the current time, the current frame is expired
    let didTimeout = deadLineTime <= currentTime;

    if ((didTimeout || timeRemaining() > 0) && pendingCallback) { pendingCallback({ didTimeout, timeRemaining }); }};window.requestIdleCallback = (callback) = > {
    requestAnimationFrame((rafTime) = > {
        console.log(rafTime);
        // Each frame start time plus 16.6 is the end time
        deadLineTime = rafTime + activeTime;
        pendingCallback = callback;
        // Add a macro task to execute after drawing
        channel.port1.postMessage("hello");
    });
};
Copy the code

So that’s the pre-knowledge of Fiber.

To realize the React Fiber

Fiber is divided into two stages

Reconcilation: It can be interrupted to find out all changes, such as node additions, deletations, attribute changes, etc. (the following life cycles are invoked during this phase)

  1. componentWillMount
  2. componentWillReceiveProps
  3. static getDerivedStateFromProps
  4. shouldComponentUpdate
  5. componentWillUpdate

Commit phase: non-interruptible, changes are executed (the following lifecycle is called during this phase)

  1. componentDidMount
  2. componentDidUpdate
  3. componentWillUnmount

Reconcilation

1, first traversal the current node of the child nodes, after the brother node, finally uncle node 2, all the child nodes of their own completed, their own completion

This phase involves finding all node changes, which are called side effects in React, and traversing the rules as shown in the figure

/ / the root node
let workInProgressRoot = {
    stateNode: container, // This fiber corresponds to the DOM node
    props: {
        children: [element],
    },
};

// Next unit of work
let nextUnitOfWork = workInProgressRoot;

function workLoop() {
    // executes when the next unit of work exists, and returns the next unit of work
    while (nextUnitOfWork) {
        nextUnitOfWork = performanceUnitOfWork(nextUnitOfWork);
    }

    if (!nextUnitOfWork) {
        commitRoot();
    }
}

function performanceUnitOfWork(workInProgressFiber) {
    // 1. Create a real DOM without mounting it. 2
    beginWork(workInProgressFiber);

    // First iterate over the child nodes
    if (workInProgressFiber.child) {
        return workInProgressFiber.child;
    }

    while (workInProgressFiber) {
        // If there is no son, the current node is already finished
        completeUnitOfWork(workInProgressFiber);
        
        // Iterate over the sibling nodes
        if (workInProgressFiber.sibling) {
            return workInProgressFiber.sibling;
        }

        // Iterate over the parent nodeworkInProgressFiber = workInProgressFiber.return; }}function beginWork(workInProgressFiber) {
    console.log("beginWork", workInProgressFiber.props.id);

    // Only create elements, not mount them
    if(! workInProgressFiber.stateNode) { workInProgressFiber.stateNode =document.createElement(
            workInProgressFiber.type
        );

        for (let key in workInProgressFiber.props) {
            if(key ! = ="children") { workInProgressFiber.stateNode[key] = workInProgressFiber.props[key]; }}}// Create a subfiber
    let prevFiber;
    if (Array.isArray(workInProgressFiber.props.children)) {
        workInProgressFiber.props.children.forEach((item, index) = > {
            let childFiber = {
                type: item.type,
                props: item.props,
                return: workInProgressFiber, // The parent node of the current node
                effectTag: "PLACEMENT".The // tag indicates that the corresponding DOM needs to be inserted into the page
            };

            // Mount the first child to the child of the current node, and the rest of the child nodes, mount to the first child's sibling
            if (index === 0) {
                workInProgressFiber.child = childFiber;
            } else{ prevFiber.sibling = childFiber; } prevFiber = childFiber; }); }}Copy the code

Collection of side effects

The Fiber tree is traversed and nodes with side effects are collected to form a unidirectional linked list.

Each node is merged upward into the Effect list, with firstEffect denoting the first subfiber with side effects and lastEffect denoting the last subfiber with side effects. In the middle, nextEffect is used to form a unidirectional linked list.

The effect List order is the same as the completion order of the fiber node traversal.

function completeUnitOfWork(workInProgressFiber) {
    console.log("completeUnitOfWork", workInProgressFiber.props.id);
    // Build side effects
    let returnFiber = workInProgressFiber.return; / / the parent node

    if (returnFiber) {
        // Mount the list of side-effect children of the current fiber to the parent node
        if(! returnFiber.firstEffect) { returnFiber.firstEffect = workInProgressFiber.firstEffect; }if (workInProgressFiber.lastEffect) {
            if (returnFiber.lastEffect) {
                returnFiber.lastEffect.nextEffect =
                    workInProgressFiber.firstEffect;
            }

            returnFiber.lastEffect = workInProgressFiber.lastEffect;
        }

        // Mount yourself
        if (workInProgressFiber.effectTag) {
            if (returnFiber.lastEffect) {
                returnFiber.lastEffect.nextEffect = workInProgressFiber;
            } else{ returnFiber.firstEffect = workInProgressFiber; } returnFiber.lastEffect = workInProgressFiber; }}}Copy the code

commit

Update the view from the Effect List.


function workLoop() {
    // executes when the next unit of work exists, and returns the next unit of work
    while (nextUnitOfWork) {
        nextUnitOfWork = performanceUnitOfWork(nextUnitOfWork);
    }

    if (!nextUnitOfWork) {
        commitRoot();
    }
}


// Insert the page
function commitRoot() {
    let currentFiber = workInProgressRoot.firstEffect;

    while (currentFiber) {
        if (currentFiber.effectTag === "PLACEMENT") {
            currentFiber.return.stateNode.appendChild(currentFiber.stateNode);
        }

        currentFiber = currentFiber.nextEffect;
    }

    workInProgressRoot = null;
}
Copy the code

The full code can be viewed at: Github address.

conclusion

This article is just a simple implementation of React Fiber, a brief introduction of the principle of React Fiber, the reason for the introduction of Fiber, but there are still a lot of things not introduced, such as task scheduling priority, task interruption and recovery, you can check the source code if you are interested

Standing on the shoulders of giants

This article is just a brief introduction. In fact, the React implementation is much more complicated than this one. If you want to learn more, read the following article

  • React Fiber Architecture
  • Step into the world of React Fiber
  • What is React Fiber
  • Penetrate the React Fiber architecture and source code
  • Scheduling in React
  • Fiber Principles: Contributing To Fiber
  • Flarnie Marchan – Ready for Concurrent Mode?
  • Didact Fiber: Incremental reconciliation
  • Lin Clark – A Cartoon Intro to Fiber – React Conf 2017

Nanjing S300 Cloud Information Technology Co., LTD. (CH300) was founded on March 27, 2014. It is a mobile Internet enterprise rooted in Nanjing, currently located in Nanjing and Beijing. After 7 years of accumulation, the cumulative valuation has reached 5.2 billion times, and has been favored by many high-quality investment institutions at home and abroad, such as Sequoia Capital, SAIC Industrial Fund, etc. S300 Cloud is an excellent domestic independent third-party SaaS service provider of auto transaction and finance, which is based on artificial intelligence and takes the standardization of auto transaction pricing and auto financial risk control as the core product.

Welcome to join s300 Cloud and witness the booming development of the automobile industry together. Look forward to walking hand in hand with you! Java development, Java internship, PHP internship, testing, testing, product manager, big data, algorithm internship, hot recruitment… Website: www.sanbaiyun.com/ Resume: [email protected], please indicate from 😁