likes
comments
collection
share

浅谈Fiber架构的工作流程

作者站长头像
站长
· 阅读数 24

Fiber 起源

Fiber架构诞生于React16,是为了解决React15及之前版本的更新不可中断问题的。

堆栈协调器 Stack Reconciler

我们知道,React在工作的时候中有一个重要的阶段叫做协调阶段Reconcile,在React15的时候,React采用的还是堆栈协调Stack Reconciler,之所以把它成为堆栈协调,是因为React是使用递归来构建虚拟Dom树(React 15的叫法)的,构建过程中,数据被保存在递归调用栈中。由于递归是同步执行的,所以它一旦执行就只能执行完,不能被中途打断。这导致浏览器在执行代码时,Stack Reconciler 经常由于需要协调非常多的节点而耗费大量时间,而浏览器的UI渲染工作迟迟得不到执行,这会导致浏览器产生肉眼可见的掉帧现象。

Fiber协调器 Fiber Reconciler

为了解决Stack Reconciler的递归调用,不可中断问题,React团队在React16发布时推出了全新的Fiber架构,旨在解决老版本的更新不可中断问题。React团队提出了一种新的模式**Concurrent Mode**,一个大的同步任务可以分成许多小的同步任务,在浏览器运作的时候,平均的把这些小的同步任务塞到每一帧的一小块时间里执行,这种做法我们称为可中断的异步更新。而我们知道,在React15的时候,同步任务由于架构的限制,是不可切分的,一旦暂停任务只能全部中断。但是权限的Fiber架构可以保存更新时的运行状态,以便下次调用时可以继续上次的更新。所以说,Fiber架构为Concurrent Mode的推行打下了基础,这种可中断的更新解决了卡顿掉帧的问题,也带给了用户更好的交互体验。

Fiber工作流程

在React15中我们知道有虚拟DOM树,用来建立和真实DOM的映射关系,在Fiber架构中我们把种映射成为Fiber树。一棵Fiber由一个当前应用根节点FiberRootNode和当前组件树根节点rootFiber构成,rootFiber实际上是一个FiberNode,它又连接了由其他FiberNode组成的子树。FiberRootNode通过current指针连接当前组件树rootFiber。这里我们用了当前组件树这个词,其实是为了引出Fiber架构下的双缓存机制。

双缓存机制

我们在图像处理的时候,往往会经历渲染画面-清除画面-重新渲染画面这个过程,往往清除画面后进行重绘的时候,可能会比较耗时,这时候用户就会感知到闪屏的现象。如果我们在内存中进行当前帧画面的构建,构建完毕后直接替换之前的画面,省去清屏的步骤,这样就节省了很多时间,很大程度上改善了用户体验。所以在React中,我们也使用了双缓存机制,即系统中始终存在着两棵Fiber树,一棵对应的是当前DOM在屏幕上显示的画面,被称作current,此时我们称其为当前组件树,一棵是在内存中进行构建的新的Fiber树,被称作workInProgress,此时我们称其为正在构建中的组件树

Fiber树示例

在如下的代码所渲染的组件中

function App() {
  return (
    <div className="App">
      <header>
        <div>
            Hello React
        </div>
        <section>
          Happy Hacking
        </section>
      </header>
    </div>
  );
}

一棵完整的Fiber树示例如图所示:根节点FiberRootNode会使用current指向当前组件树,当前组件树的根节点rootFIber会使用child指向子节点,如果存在多个子节点,那么子节点与子节点之间又会使用sibling指针连接。浅谈Fiber架构的工作流程

Fiber首屏渲染

我们换一个简单的示例,来更好的理解渲染流程:

function App() {
  const [num , setNum] = useState(0);
  return (
        <p onClick={()=>setNum(num + 1)}>
           {num}
        </p>
  );
}

在一开始,React会先建立FiberRootNode和rootFiber作为初始的Fiber树,FiberRootNode的current指针指向rootFiber,此时rootFiber是为空的。然后根据组件树返回的jsx对象(就是createElement的返回值对象)在render阶段创建新的rootFiber,这一步是递归的创建workInProgress,创建完workInProgress后,然后在commit阶段把这棵树渲染到页面上,此时修改current指针指向workInProgress,使其成为新的current树。这就是Fiber的首屏渲染流程。current和workInProgress通过alternate互相连接,我们后面会讲到为何这么做。浅谈Fiber架构的工作流程

Fiber树更新

在我们点击p使得页面触发更新后,React会在内存中重新构建一棵完整的Fiber树,也就是workInProgress,在构建完成后会直接让current指针指向它,然后render阶段就会基于这个新的current进行渲染。在此过程中我们可以使用Diff算法决定是否复用current树中的节点,省去创建节点的流程,进一步加快渲染过程。

节点复用

前面我们说过了,在页面更新时,由于React的双缓存机制,在渲染页面的时候,会先从内存中构建一棵Fiber树,等构建完毕后,直接改变current指针的指向替换掉当前的Fiber树,达到页面更新的目的。所以在构建workInProgress树的时候,我们实际上还有一棵current树,由于大多数更新不过是某个样式的改变或数据小规模更新,导致UI变化不是很大,如果此时我们还在内存中重新的从无到有渲染一棵完整的Fiber树,是很耗时的,所以我们可以基于current 树来复用一些节点创建workInProgress树,我们会使用Diff算法(有兴趣的小伙伴可以自行学习)来决定是否复用节点,要复用的节点就是current.alternate。

有小伙伴可能现在就要问了,current.alternate不是一棵完整的树吗,怎么可以直接复用呢?其实在构建workInProgress时,current也在不断的变化,和workInProgress同步移动。

浅谈Fiber架构的工作流程