通过上一节的学习,我们了解了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 fiber
,workInProgress 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树
,通过current
与workInProgress
的替换,完成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'));
- 首次执行
ReactDOM.render
会创建fiberRootNode
(源码中叫fiberRoot
)和rootFiber
。其中fiberRootNode
是整个应用的根节点,rootFiber
是<App/>
所在组件树的根节点。
之所以要区分fiberRootNode
与rootFiber
,是因为在应用中我们可以多次调用ReactDOM.render
渲染不同的组件树,他们会拥有不同的rootFiber
。但是整个应用的根节点只有一个,那就是fiberRootNode
。
fiberRootNode
的current
会指向当前页面上已渲染内容对应Fiber树
,即current Fiber树
。
fiberRootNode.current = rootFiber;
由于是首屏渲染,页面中还没有挂载任何DOM
,所以fiberRootNode.current
指向的rootFiber
没有任何子Fiber节点
(即current Fiber树
为空)。
- 接下来进入
render阶段
,根据组件返回的JSX
在内存中依次创建Fiber节点
并连接在一起构建Fiber树
,被称为workInProgress Fiber树
。(下图中右侧为内存中构建的树,左侧为页面显示的树)
在构建workInProgress Fiber树
时会尝试复用current Fiber树
中已有的Fiber节点
内的属性,在首屏渲染
时只有rootFiber
存在对应的current fiber
(即rootFiber.alternate
)。
3. 图中右侧已构建完的workInProgress Fiber树
在commit阶段
渲染到页面。
此时DOM
更新为右侧树对应的样子。fiberRootNode
的current
指针指向workInProgress Fiber树
使其变为current Fiber 树
。
update时
- 接下来我们点击
p节点
触发状态改变,这会开启一次新的render阶段
并构建一棵新的workInProgress Fiber 树
。
和mount
时一样,workInProgress fiber
的创建可以复用current Fiber树
对应的节点数据。
这个决定是否复用的过程就是Diff算法,后面章节会详细讲解
workInProgress Fiber 树
在render阶段
完成构建后进入commit阶段
渲染到页面上。渲染完毕后,workInProgress Fiber 树
变为current Fiber 树
。
总结
本文介绍了Fiber树
的构建与替换过程,这个过程伴随着DOM
的更新。
那么在构建过程中每个Fiber节点
具体是如何创建的呢?我们会在架构篇
的render阶段讲解。
参考资料
此Demo
会在如下时机在控制台打印信息:
- 构建
WorkInProgrss Fiber
时 - 在渲染完毕后,
workInProgress Fiber 树
变为current Fiber 树
时
下一节:通过本章的学习,我们了解了React的Scheduler-Reconciler-Renderer架构体系,在结束本章前,我想介绍几个源码内的术语: