React Fiber 数据结构揭秘

作者: 牧云云 | 来源:发表于2019-02-23 21:43 被阅读2次
    image

    个人博客

    此章节会通过两个 demo 来展示 Stack Reconciler 以及 Fiber Reconciler 的数据结构。

    image

    首先用代码表示上图节点间的关系。比如 a1 节点下有 b1、b2、b3 节点, 就可以把它们间的关系写成 a1.render = () => [b1, b2, b3];

    var a1 = { name: 'a1', render = () => [b1, b2, b3] }
    var b1 = { name: 'b1', render = () => [c1] }
    var b2 = { name: 'b2', render = () => [c2] }
    var b3 = { name: 'b3', render = () => [] }
    var c1 = { name: 'c1', render = () => [d1] }
    var c2 = { name: 'c2', render = () => [] }
    var d1 = { name: 'd1', render = () => [d2] }
    var d2 = { name: 'd2', render = () => [] }
    

    Stack Reconciler

    React 16 之前,节点之间的关系可以用数据结构中树的深度遍历来表示。

    如下实现 walk 函数, 将深度遍历的节点打印出来。

    walk(a1)
    
    function walk(instance) {
      if (!instance) return
      console.log(instance.name)
      instance.render().map(walk)
    }
    

    输出结果为: a1 b1 c1 d1 d2 b2 c2 b3

    Fiber Reconciler

    React 16 中,节点之间的关系可以用数据结构中的链表来表示。

    节点之间的链表有三种情形, 用图表示如下:

    image
    1. 父节点到子节点(红色虚线)
    2. 同层节点(黄色虚线)
    3. 子节点到父节点(蓝色虚线)

    父节点指向第一个子节点, 每个子节点都指向父节点,同层节点间是单向链表。

    首先, 构建节点的数据结构, 如下所示:

    var FiberNode = function(instance) {
      this.instance = instance
      this.parent = null
      this.sibling = null
      this.child = null
    }
    

    然后创建一个将节点串联起来的 connect 函数:

    var connect = function(parent, childList) {
      parent.child = childList.reduceRight((prev, current) => {
        const fiberNode = new FiberNode(current)
        fiberNode.parent = parent
        fiberNode.sibling = prev
        return fiberNode
      }, null)
    
      return parent.child
    }
    

    在 JavaScript 中实现链表的数据结构可以巧用 reduceRight

    connect 函数中实现了上述链表关系。可以像这样使用它:

    var parent = new FiberNode(a1)
    var childFirst = connect(parent, a1.render())
    

    这样子便完成了 a1 节点指向 b1 节点的链表、b1、b2、b3 节点间的单向链表以及 b1、b2、b3 节点指向 a1 节点的链表。

    最后剩下 goWalk 函数将全部节点给遍历完。

    // 打印日志以及添加列表
    var walk = function(node) {
      console.log(node.instance.name)
      const childLists = node.instance.render()
      let child = null
      if (childLists.length > 0) {
        child = connect(node, childLists)
      }
      return child
    }
    
    var goWalk = function(root) {
      let currentNode = root
    
      while (true) {
        const child = walk(currentNode)
        // 如果有子节点
        if (child) {
          currentNode = child
          continue
        }
    
        // 如果没有相邻节点, 则返回到父节点
        while (!currentNode.sibling) {
          currentNode = currentNode.parent
          if (currentNode === root) {
            return
          }
        }
    
        // 相邻节点
        currentNode = currentNode.sibling
      }
    }
    
    // 调用
    goWalk(new FiberNode(a1))
    

    打印结果为 a1 b1 c1 d1 d2 b2 c2 b3

    Fiber Reconciler 的优势

    通过分析上述两种数据结构实现的代码,可以得出下面结论:

    • 基于树的深度遍历实现的 Reconciler: 一旦进入调用栈便无法暂停;
    • 基于链表实现的 Reconciler: 在 while(true) {} 的循环中, 可以通过 currentNode 的赋值重新得到需要操作的节点,而在赋值之前便可以'暂停'来执行其它逻辑, 这也是 requestIdleCallback 能得以在 Fiber Reconciler 的原因。

    相关链接

    相关文章

      网友评论

        本文标题:React Fiber 数据结构揭秘

        本文链接:https://www.haomeiwen.com/subject/unrmyqtx.html