React Fiber 漸進式遍歷詳解

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

以前寫的一篇文章,React Fiber 原理介紹,介紹了 React Fiber 的實現原理,其中的關鍵是使用Fiber鏈的數據結構,將遞歸的Stack Reconciler改寫爲循環的Fiber Reconciler。今天將手寫一個 demo,詳細講解遍歷Fiber鏈的實現方式。java

2、Stack Reconciler

假設有如下組件樹:node

clipboard.png

對應的 JS 代碼以下:segmentfault

const a1 = {name: 'a1'};
const b1 = {name: 'b1'};
const b2 = {name: 'b2'};
const b3 = {name: 'b3'};
const c1 = {name: 'c1'};
const c2 = {name: 'c2'};
const d1 = {name: 'd1'};
const d2 = {name: 'd2'};

a1.render = () => [b1, b2, b3];
b1.render = () => [];
b2.render = () => [c1];
b3.render = () => [c2];
c1.render = () => [d1, d2];
c2.render = () => [];
d1.render = () => [];
d2.render = () => [];

使用Stack Reconciler遞歸的方式來遍歷組件樹,大概是這個樣子:數據結構

function doWork(o) {
    console.log(o.name);
}

function walk(instance) {
    doWork(instance);
    
    const children = instance.render();
    children.forEach(walk);
}

walk(a1);

// 輸出結果:a1, b1, b2, c1, d1, d2, b3, c2

2、Fiber Reconciler

下面咱們用 Fiber 的數據結構來改寫遍歷過程。首先定義數據結構,而後在遍歷的過程當中經過link方法建立節點間的關係:this

// 定義 Fiber 數據結構
class Node {
    constructor(instance) {
        this.instance = instance;
        this.child = null;
        this.sibling = null;
        this.return = null;
    }
}

// 建立關係鏈
function link(parent, children) {
    if (children === null) children = [];

    // child 指向第一個子元素
    parent.child = children.reduceRight((previous, current) => {
        const node = new Node(current);
        node.return = parent;
        // sibling 指向前面處理的元素
        node.sibling = previous;
        return node;
    }, null);

    return parent.child;
}

遍歷完成後會得出以下的關係鏈:spa

clipboard.png

下面來詳細看下遍歷的過程。仍是沿用以前的walkdoWork方法名:code

function doWork(node) {
    console.log(node.instance.name);
    
    // 建立關係鏈
    const children = node.instance.render();
    return link(node, children);
}

function walk() {
    while (true) {
        let child = doWork(node);

        if (child) {
            node = child;
            continue;
        }

        if (node === root) {
            return;
        }

        while (!node.sibling) {
            if (!node.return || node.return === root) {
                return;
            }

            node = node.return;
        }

        node = node.sibling;
    }
}

const hostNode = new Node(a1);

const root = hostNode;
let node = root;

walk();

// 輸出結果:a1, b1, b2, c1, d1, d2, b3, c2

上面就是遞歸改循環的代碼了。能夠看到循環的結束條件是當前處理的節點等於根節點。在循環開始的時候,以深度優先一層一層往下遞進。當沒有子節點和兄弟節點的時候,當前節點會往上層節點回溯,直至根節點爲止。blog

下面再來看看怎麼結合requestIdleCallback API,實現漸進式遍歷。因爲完成這個遍歷所需時間實在過短,所以每處理 3 個節點,咱們sleep 1 秒,從而達到退出當前requestIdleCallback的目的,而後再建立一個新的回調任務:遞歸

function sleep(n) {
    const start = +new Date();
    while(true) if(+new Date() - start > n) break;
}

function walk(deadline) {
    let i = 1;

    while (deadline.timeRemaining() > 0 || deadline.didTimeout) {
        console.log(deadline.timeRemaining(), deadline.didTimeout);

        let child = doWork(node);

        if (i > 2) {
            sleep(1000);
        }
        i++;

        if (child) {
            node = child;
            continue;
        }

        if (node === root) {
            console.log('================ Task End ===============');
            return;
        }

        while (!node.sibling) {
            if (!node.return || node.return === root) {
                console.log('================ Task End ===============');
                return;
            }

            node = node.return;
        }

        node = node.sibling;
    }

    console.log('================ Task End ===============');

    requestIdleCallback(walk);
}

requestIdleCallback(walk);

// 輸出結果:
15.845 false
a1
15.14 false
b1
14.770000000000001 false
b2
================ Task End ===============
15.290000000000001 false
c1
14.825000000000001 false
d1
14.485000000000001 false
d2
================ Task End ===============
14.96 false
b3
14.475000000000001 false
c2
================ Task End ===============

3、總結

本文經過一個 demo,講解了如何利用React Fiber的數據結構,遞歸改循環,實現組件樹的漸進式遍歷。

相關文章
相關標籤/搜索