参考文章:React技术揭秘——什么是双缓冲
什么是双缓冲?
以 canvas 为例子,当我们用canvas绘制动画,每一帧绘制前都会调用ctx.clearRect清除上一帧的画面。
如果当前帧画面计算量比较大,我们就很容易因为绘制太慢了出现白屏。
为了解决这个问题,我们可以先在内存中绘制当前帧的动画,绘制完毕后,直接用当前帧替换上一帧,这样就省去了两帧的替换时间,不会出现从白屏到出现画面的闪烁情况。
这种在内存中构建并直接替换的技术叫做双缓冲。
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
React 哪里使用了这个技术?
render阶段开始于performSyncWorkOnRoot
或performConcurrentWorkOnRoot
方法的调用。这取决于本次更新是同步更新还是异步更新。
这两个方法的主要区别如下,但是它们都依赖于 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 时
- 由于是首屏渲染,页面中还没有挂载任何DOM,所以fiberRootNode.current指向的rootFiber没有任何子Fiber节点(即current Fiber树为空)。
- 接下来进入render阶段,根据组件返回的JSX在内存中依次创建Fiber节点并连接在一起构建Fiber树,被称为workInProgress Fiber树。(下图中右侧为内存中构建的树,左侧为页面显示的树)
强调:在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性,在首屏渲染时只有rootFiber存在对应的current fiber(即rootFiber.alternate)
这一点在下面的Update时,表现地更加明显。
- 将已构建完的workInProgress Fiber树在commit阶段渲染到页面。
此时DOM更新为右侧树对应的样子。fiberRootNode的current指针指向workInProgress Fiber树使其变为current Fiber 树。
update 时
- 接下来我们点击p节点触发状态改变,这会开启一次新的render阶段并构建一棵新的workInProgress Fiber 树。
在构建workInProgress Fiber树时会尝试复用current Fiber树中已有的Fiber节点内的属性
这个决定是否复用的过程就是Diffing算法,有关Diffing算法可以看这里
- render阶段完成构建后进入commit阶段渲染到页面上。渲染完毕后,使用 workInProgress Fiber 替换 current Fiber 树。
总结:
React 根据双缓冲的机制维护了两棵树:
- 一棵是 Fiber 树用于渲染页面;
- 一棵是 WorkInProgress Fiber 树,用于在内存中构建,然后方便在构建完成时直接替换用于渲染页面的 Fiber 树。
网友评论