React Fiber架构 -- 链表结构 (二)

218 阅读2分钟

1.为什么要选择链表结构遍历组件树?

深度优先:

var C = {
    key: 'A1',
    children: [
        {
            key: "B1",
            children: [{
                key: "c1", children: [],
            },
            {
                key: "c2", children: []
            }]
        }, {
            key: 'B2', children: []
        }
    ]
}

function walk(vdom) {
    doWork(vdom);
    vdom.children.forEach(child => {
        walk(child)
    });
}
walk(C)
function doWork(vdom) {
    console.log(vdom.key, '---key---')
    //返回 A1 B1 C1 c2 B2
}

链表结构可以避免递归,递归无法停止(直接会执行到底),中断,溯源,并且一旦嵌套层级多,当前任务量增大,很容易卡住,为了契合使用requestIdleCallback优化方案。 必须要让任务能中断。

例: 更新 使用链表结构更新 setState:

class Update {       //payload 数据或者元素
    constructor(payload, nextUpdate) {
        this.payload = payload;
        this.nextUpdate = nextUpdate;  //指向下一个节点的指针
    }
}

class UpdateQueue {

    constructor() {
        this.baseState = null;  //原状态
        this.firstUpdate = null;  //第一个更新
        this.lastUpdate = null;   //最后一个更新
    }

    enqueueUpdate(update) {
        if (this.firstUpdate == null) {
            this.firstUpdate = this.lastUpdate = update;
            //这里是初始化的时候, 初始状态和最后一个状态一起指向第一个、
        } else {
            this.lastUpdate.nextUpdate = update;  //上一个最后一个节点nextUpdate 指向自己
            this.lastUpdate = update;   //让最后一个节点指向自己
        }
    }

    //通过这个把setState 串联成一个链表。

    forceUpdate() {
        let currentState = this.baseState || {};

        let currentUpdate = this.firstUpdate;

        while (currentUpdate) {

            let nextState = typeof currentUpdate.payload == 'function'
                ? currentUpdate.payload(currentState) : currentUpdate.payload;
            currentState = { ...currentState, ...nextState }; //使用当前更新得到新的状态
            //使用当前更新得到新的状态
            currentUpdate = currentUpdate.nextUpdate;   // 找下一个节点      

        }

        this.firstUpdate = this.lastUpdate = null;
        // 更新完之后要把链表清空

        this.baseState = currentState;
        return currentState;
    }
}

let queue = new UpdateQueue();

queue.enqueueUpdate(new Update({ name: 'lianjie' }));

queue.enqueueUpdate(new Update({ number: 0 }));

queue.enqueueUpdate(new Update((state) => { return { number: state.number + 1 } }));

queue.enqueueUpdate(new Update((state) => { return { number: state.number + 1 } }));

queue.forceUpdate();

屏幕截图 2022-04-04 091741.png

可以看到结果已经生成。

2.实际React 链表结构

屏幕截图 2022-04-04 084104.png

A1 这个节点包含三个指针,return,sibling,child.

注意: return的永远是父级的第一个节点。 C2->B1 B2->A1。

在查找结构中:

屏幕截图 2022-04-04 085748.png

绿色的线代表执行的顺序,也就是会先深度优先遍历。找到最深的孩子之后(C1,没有child终止),再看这个孩子有没有兄弟,有兄弟就继续sibling,直到C2没有sibling,之后再return回上一级的直接父级。

顺序 : A1-> B1 -> C1 -> C2 -> B1-> B2 ->A1

整体就像在画三角形, 比如(B1 -> c1 -> c2 -> B1)。

我们用简单实例说明:

    let A1 = { type: "div", key: "A1" };
    let B1 = { type: "div", key: "B1", return: A1 };
    let B2 = { type: "div", key: "B2", return: A1 };
    let C1 = { type: "div", key: "C1", return: B1 };
    let C2 = { type: "div", key: "C2", return: B1 };

    A1.child = B1;
    B1.sibling = B2;
    B1.child = C1;
    C1.sibling = C2;

上述代码用来描述上图结构关系。

    ... 
    let root = A1;   //保存头部节点

    let nextUnitFWork = null;  //表示当前的fiber节点

    function workLoop(deadline) {
        //这里承接我第一篇提到的空闲时间>0 才执行,不然走下一帧
        while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextUnitFWork) {
            nextUnitFWork = performUnitOfWork(nextUnitFWork);
        }
        if (nextUnitFWork) {
          window.requestIdleCallback(workLoop, { timeout: 1000 })
        }
    }
    
    function performUnitOfWork(fiber) {      //A1
        beginWork(fiber);      //处理此fiber
        if (fiber.child) {     //如果有大儿子  返回 ,一级一级找到最深的那个
            return fiber.child;
        }
       
        while (fiber) {
          //最深的儿子找到之后 再找兄弟
            completeUnitOfWork(fiber);
            if (fiber.sibling) {
                return fiber.sibling;  //如果有兄弟返回兄弟
            }
            fiber = fiber.return;     //没有兄弟之后返回父级再循环找
        }
    }
    
    function beginWork(fiber) {
        console.log("开始", fiber.key); //处理fiber
    }

    function completeUnitOfWork(fiber) {
        console.log(fiber.key, '结束'); //处理fiber
    }

    nextUnitFWork = A1;

    window.requestIdleCallback(workLoop, { timeout: 1000 })  //第一次执行
    

返回结果:

屏幕截图 2022-04-04 084104.png

参考: zhuanlan.zhihu.com/p/54196962