在react中,fiber链表是怎么遍历的?
2024-04-09 17:00:01  阅读数 1076
Fiber是对react核心算法的重构,react16以上版本引入了fiber架构,其中的设计思想很值得我们去学习。
那fiber是什么呢?
  • fiber是一个执行单元
  • fiber也是一种数据结构
在没引入fiber之前,React会递归比对VirtualDOM树,找出需要变动的节点,然后同步更新它们。这个过程React称为Reconciliation(协调)。在Reconciliation过程中,React会一直占用着浏览器资源,如果更新节点庞大,那么用户触发的事件可能得不到回馈或者出现卡顿。
fiber的出现则是把庞大的更新节点分割为一个个小的任务单元,浏览器可以在react和响应时间中切换控制权,从而达到优化效果。
fiber的可中断、继续是基于 浏览器的 requestIdleCallback api实现的。目前大多浏览设备每秒刷新60次,requestIdleCallback 函数每一帧会返回这一秒渲染完成后剩余的时间,React就会检查现在还剩多少时间,如果没有时间就将控制权交还浏览器;然后继续进行下一帧的渲染。

虚拟dom转化成fiber后,会有这几个属性,去指向下一个执行单元。

  • child(child指的是第一个子节点而不是所有的子节点)
  • sibling 指的是下一个兄弟节点(弟弟)
  • return 指向父节点
<div id="A1">
  <div id="B1">
     <div id="C1"></div>
     <div id="C2"></div>
  </div>
  <div id="B2"></div>
<div>

这样的jsx如果要转成fiber则会是这个样子

详情可以戳 -> 在react中,虚拟dom是如何转化为fiber链表的?

// element.js
let A1 = { key: 'A1', type: 'div' }
let B1 = { key: 'B1', type: 'div', return: A1 }
let B2 = { key: 'B2', type: 'div', return: A1 }
let C1 = { key: 'C1', type: 'div', return: B1 }
let C2 = { key: 'C2', type: 'div', return: B1 }

A1.child = B1
B1.sibling = B2
B1.child = C1
C1.sibling = C2
module.exports = A1

fiber的遍历顺序是由根节点开始的,优先级 child > sibling > return
所以遍历的先后顺序是

  • A1.child (B1)
  • B1.child (C1)
  • C1.sibling (C2) C1没有child了,就去找它的兄弟
  • C2.return (B1) C2 既没有child,又没有sibling,就去找它的 父节点 return
  • B1.sibling (B2) B1的child已经被遍历过了,就去找它的sibling
  • B2.return (A1)

开始遍历

let rootFiber = require('./element')

let nextUnitOfWork = null

function workLoop() {
  while(nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }
}

function performUnitOfWork(currentFiber) { // 返回下一个执行单元
   beginWork(currentFiber)
   if (currentFiber.child) {
    return currentFiber.child
  }
  // 如果能走到这一步,证明遍历到 C1 了
  while(currentFiber) { // 因为此处可能 sibling不一定只有一个,所以要进入while循环
    if(currentFiber.sibling) {
      return currentFiber.sibling
    }
    // 如果没有sibling了,就会到其父节点
    return currentFiber.return
  }
}

function beginWork(currentFiber) {
  // 本章主要记录fiber是怎么遍历的,所以就不去做别的事情啦
  console.log(currentFiber.key)
}

nextUnitOfWork = rootFiber

workLoop()
写到这块是不是少了些什么东西呢?哦对,没有写怎么去让它变成可中断、可恢复的遍历,那就需要祭出上面提出的requestIdleCallback了
我们只需要对workLoop作出小小改动即可
function workLoop(deadline) {
    // timeRemaining() 是指当前渲染完留给react调度的时间
    while ((deadline.timeRemaining() > 1 && nextUnitOfWork) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
    if (!nextUnitOfWork) {
        console.log('render阶段遍历结束')
    } else {
        requestIdleCallback(workLoop, { timeout: 500 });
    }
}

requestIdleCallback(workLoop, { timeout: 500 });
timeout是指的超时时间,意思是,如果超过500ms还没做我的事的话,那你必须放下手头工作,立刻去继续执行workLoop。
这样我们关于fiber链表是怎么遍历的就完成啦!

资料参考: 从零实现React16 Fiber架构与Hooks源码