fiber节点渲染

大家好,我是万维读客的讲师曹欢欢。上节我们搭建好了异步渲染调度框架,本节我们学习如何组装构建fiber链表数据结构,实现异步渲染fiber树节点。

节点遍历

这里可能先要补充一点树的遍历的基本知识,比如下面这个树形结构:

            A
          / | \
         B  C  D
        /
       E
      /  \
     F    G

遍历这个树形结构一般有三种方法:前序遍历、中序遍历、后序遍历;

我们看三个动画对比就比较清楚了:

给个比较的案例:

这些图是参考网上的,谢谢网友分享。

我们前面同步渲染的时候,还记得处理children的递归吗

renderElement(ele, parent) {
    const node = ele.type === 'HostText'?document.createTextNode(''):document.createElement(ele.type);
    Object.keys(ele.props).filter(key => key != 'children').forEach(key => {
        node[key] = ele.props[key];
    })
    ele.props.children.forEach(child => {
        this.renderElement(child, node);
    })
    parent.appendChild(node);
}

这个实际上就是一种后序遍历的实现,在fiber异步渲染中为了实现中断,就不能再通过递归的方式来处理了,需要我们在处理一个节点之后,能够找到下个节点。

中序遍历

React中的解决方案是通过树遍历的方法遍历整个虚拟DOM树,遍历过程中初始化对应的Fiber节点,构造一个遍历后的顺序列表,然后逐个调度render渲染。

假设采用先序遍历后形成的Fiber节点链表如下:

DOM树
            A
          / | \
         B  C  D
        /
       E
      /  \
     F    G

fiber节点链表
----------------
根节点A ---> child节点B ---> child节点E ---> child节点F ---> sibling节点G ---> sibling节点C ---> sibling节点D

实现节点渲染

我们参考上一节学习的fiber的原理和react相关的代码,修改treat代码结构如下:

/**
 * 1. 处理当前执行的fiber节点,插入到父级中
 * 2. 初始化children中的fiber节点,返回下一个要执行的fiber节点next
 */
performUnitOfWork(fiber) {
    if (!fiber.stateNode) {
        fiber.stateNode = fiber.type === 'HostText' ? document.createTextNode('') : document.createElement(fiber.type);
        Object.keys(fiber.props).filter(key => key != 'children').forEach(key => {
            fiber.stateNode[key] = fiber.props[key];
        })
    }
    if(fiber.return){
        fiber.return.stateNode.appendChild(fiber.stateNode);
    }
    // 用链表处理child
    let preSibling = null;
    fiber.props.children.forEach((child, idx) => {
        const fiberChild = {
            type: child.type,
            stateNode: null,
            props: child.props,
            return: fiber,
        }
        if(idx == 0){
            fiber.child = fiberChild;
        }else{
            preSibling.sibling = fiberChild;
        }
        preSibling = fiberChild;
    })
    
    return this.getNextFiber(fiber);
}
/**
 * 先序遍历顺序
 * child-》sibling
 */
getNextFiber(fiber){
    if(fiber.child){
        return fiber.child;
    }

    let nextFiber = fiber;
    while(nextFiber){
        if(nextFiber.sibling){
            return nextFiber.sibling;
        }else{
            nextFiber = nextFiber.return;
        }
    }
    return null;
}

编写完之后查看我们的测试用例执行情况,顺利执行,表示代码完成了异步渲染。

 RERUN  treact03/jsx.test.jsx x25

 ✓ treact03/jsx.test.jsx (1) 505ms
   ✓ async render (1) 505ms
     ✓ render in async 503ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  12:51:09
   Duration  519ms

好了,这里我们实现了React Fiber的异步渲染,可能代码还不是很全面,喜欢动手的同学可以补充完善代码。也可以把上面的遍历算法改成中序、后序遍历试一下呢。

参考

  1. 树的遍历:https://developer.aliyun.com/article/978343
  2. 中序遍历:https://juejin.cn/post/7012772225644232741
  3. performConcurrentWorkOnRoot 源码:https://github.com/facebook/react/blob/4f29ba1cc52061e439cede3813e100557b23a15c/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L824
  4. workLoopConcurrent源码:https://github.com/facebook/react/blob/4f29ba1cc52061e439cede3813e100557b23a15c/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1824-L1829
  5. Fiber节点:https://github.com/facebook/react/blob/4f29ba1cc52061e439cede3813e100557b23a15c/packages/react-reconciler/src/ReactInternalTypes.js


请遵守《互联网环境法规》文明发言,欢迎讨论问题
扫码反馈

扫一扫,反馈当前页面

咨询反馈
扫码关注
返回顶部