通过上一节的学习,我们了解了Fiber是什么,知道Fiber节点可以保存对应的DOM节点

相应的,Fiber节点构成的Fiber树就对应DOM树

那么如何更新DOM呢?这需要用到被称为“双缓存”的技术。

# 什么是“双缓存”

当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。

如果当前帧画面计算量比较大,导致清除上一帧画面到绘制当前帧画面之间有较长间隙,就会出现白屏。

为了解决这个问题,我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。

这种在内存中构建并直接替换的技术叫做双缓存

React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。

# 双缓存 Fiber 树

React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树

current Fiber树中的Fiber节点被称为current fiberworkInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;

React应用的根节点通过使current指针在不同Fiber树rootFiber间切换来完成current Fiber树指向的切换。

即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树

每次状态更新都会产生新的workInProgress Fiber树,通过currentworkInProgress的替换,完成DOM更新。

接下来我们以具体例子讲解mount时update时的构建/替换流程。

# mount 时

考虑如下例子:

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

ReactDOM.render(<App />, document.getElementById("root"));
  1. 首次执行ReactDOM.render会创建fiberRootNode(源码中叫fiberRoot)和rootFiber。其中fiberRootNode是整个应用的根节点,rootFiber<App/>所在组件树的根节点。

之所以要区分fiberRootNoderootFiber,是因为在应用中我们可以多次调用ReactDOM.render渲染不同的组件树,他们会拥有不同的rootFiber。但是整个应用的根节点只有一个,那就是fiberRootNode

fiberRootNodecurrent会指向当前页面上已渲染内容对应Fiber树,即current Fiber树

rootFiber
fiberRootNode.current = rootFiber;

由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。

  1. 接下来进入render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建Fiber树,被称为workInProgress Fiber树。(下图中右侧为内存中构建的树,左侧为页面显示的树)

在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)。

workInProgressFiber
  1. 图中右侧已构建完的workInProgress Fiber树commit阶段渲染到页面。

此时DOM更新为右侧树对应的样子。fiberRootNodecurrent指针指向workInProgress Fiber树使其变为current Fiber 树

workInProgressFiberFinish

# update 时

  1. 接下来我们点击p节点触发状态改变,这会开启一次新的render阶段并构建一棵新的workInProgress Fiber 树
wipTreeUpdate

mount时一样,workInProgress fiber的创建可以复用current Fiber树对应的节点数据。

这个决定是否复用的过程就是 Diff 算法,后面章节会详细讲解

  1. workInProgress Fiber 树render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,workInProgress Fiber 树变为current Fiber 树
currentTreeUpdate

# 总结

本文介绍了Fiber树的构建与替换过程,这个过程伴随着DOM的更新。

那么在构建过程中每个Fiber节点具体是如何创建的呢?我们会在架构篇render 阶段讲解。

# 参考资料

Fiber 树的创建与切换 Demo

Demo会在如下时机在控制台打印信息:

  • 构建WorkInProgrss Fiber

  • 在渲染完毕后,workInProgress Fiber 树变为current Fiber 树

关注公众号 魔术师卡颂,后台回复812获得在线 Demo 地址