美文网首页
React 中的双缓冲机制

React 中的双缓冲机制

作者: 弱冠而不立 | 来源:发表于2021-01-27 10:15 被阅读0次

    参考文章:React技术揭秘——什么是双缓冲

    什么是双缓冲?

    以 canvas 为例子,当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。
    如果当前帧画面计算量比较大,我们就很容易因为绘制太慢了出现白屏。
    为了解决这个问题,我们可以先在内存中绘制当前帧的动画,绘制完毕后,直接用当前帧替换上一帧,这样就省去了两帧的替换时间,不会出现从白屏到出现画面的闪烁情况。
    这种在内存中构建并直接替换的技术叫做双缓冲
    React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。

    React 哪里使用了这个技术?

    render阶段开始于performSyncWorkOnRootperformConcurrentWorkOnRoot方法的调用。这取决于本次更新是同步更新还是异步更新。

    未使用 Concurrent,同步更新 使用 Concurrent模式,进行异步更新

    这两个方法的主要区别如下,但是它们都依赖于 workInProgress

    // performSyncWorkOnRoot会调用该方法
    function workLoopSync() {
      while (workInProgress !== null) {
        performUnitOfWork(workInProgress);
      }
    }
    
    // performConcurrentWorkOnRoot会调用该方法
    function workLoopConcurrent() {
      while (workInProgress !== null && !shouldYield()) {
        performUnitOfWork(workInProgress);
      }
    }
    

    源码中,workInworkInProgress 是通过 createWorkInworkInProgress 创建的,createWorkInworkInProgress 主要逻辑如下:

    // 这里入参中的 current 传入的是现有树结构中的 rootFiber 对象
    function createWorkInProgress(current, pendingProps) {
      var workInProgress = current.alternate;
      // ReactDOM.render 触发的首屏渲染将进入这个逻辑
      if (workInProgress === null) {
        // 这是需要你关注的第一个点,workInProgress 是 createFiber 方法的返回值
        workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
        workInProgress.elementType = current.elementType;
        workInProgress.type = current.type;
        workInProgress.stateNode = current.stateNode;
        // 这是需要你关注的第二个点,workInProgress 的 alternate 将指向 current
        workInProgress.alternate = current;
        // 这是需要你关注的第三个点,current 的 alternate 将反过来指向 workInProgress
        current.alternate = workInProgress;
      } else {
        // else 的逻辑此处先不用关注
      }
    
      // 以下省略大量 workInProgress 对象的属性处理逻辑
      // 返回 workInProgress 节点
      return workInProgress;
    }
    

    重点如下:

    • createWorkInProgress 将调用 createFiber,workInProgress是 createFiber 方法的返回值(createFiber 返回的就是一个 FiberNode)
    • workInProgress 的 alternate 将指向 current
    • current 的 alternate 将反过来指向 workInProgress

    其中还需要重点注意到的一行代码是:

    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    

    workInProgress 节点其实就是 current 节点(即 rootFiber)的副本。
    再结合 current 指向 rootFiber 对象(同样是 FiberNode 实例),以及 current 和 workInProgress 通过 alternate(中文含义为备份) 互相连接这些信息,我们可以分析出这波操作执行完之后,整棵树的结构应该如下图所示:

    举例说明

    考虑如下例子:

    function App() {
      const [num, add] = useState(0);
      return (
        <p onClick={() => add(num + 1)}>{num}</p>
      )
    }
    
    ReactDOM.render(<App/>, document.getElementById('root'));
    
    mount 时
    1. 由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。
    此时 currnent 尚未关联 workInProgress,也就是说内存中备用的 workInProgress Fiber 树尚未存在,还是 mount阶段
    1. 接下来进入render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建Fiber树,被称为workInProgress Fiber树。(下图中右侧为内存中构建的树,左侧为页面显示的树)

    强调:在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)
    这一点在下面的Update时,表现地更加明显。

    1. 将已构建完的workInProgress Fiber树在commit阶段渲染到页面。
      此时DOM更新为右侧树对应的样子。fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树。
    update 时
    1. 接下来我们点击p节点触发状态改变,这会开启一次新的render阶段并构建一棵新的workInProgress Fiber 树。
      在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性

    这个决定是否复用的过程就是Diffing算法,有关Diffing算法可以看这里

    1. render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,使用 workInProgress Fiber 替换 current Fiber 树。

    总结:

    React 根据双缓冲的机制维护了两棵树:

    • 一棵是 Fiber 树用于渲染页面;
    • 一棵是 WorkInProgress Fiber 树,用于在内存中构建,然后方便在构建完成时直接替换用于渲染页面的 Fiber 树。

    相关文章

      网友评论

          本文标题:React 中的双缓冲机制

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