React设计原理-commit阶段
renderer
(最常用的renderer--ReactDOM)工作的阶段被称为commit
阶段, 将各种副作用commit
到宿主环境UI中. render(reconciler工作的阶段)阶段流程可能被打断, 而commit阶段开始后会同步执行至完毕. 整个阶段分为BeforeMutation
、Mutation
、Layout
三个子阶段.
流程概览
开始于commitRoot(root)
, root就是本次更新所属FiberRootNode
, root.finishedWork代表Wip HostRootNode
(render阶段构建的 Wip Fiber Tree的HostRootNode).
在三个子阶段执行前, 先判断本次更新是否涉及与三个子阶段相关的副作用:
// Wip HostRootNode的子孙元素存在的副作用
const subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
// Wip HostRootNode本身存在的副作用
const rootHasEffects =(finishedWork.flags &( BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
if(subtreeHasEffects || rootHasEffects){
// ... 当`Wip HostRootNode`或其子孙存在副作用flags时, 进入三个子阶段
} else {
// ... 本次更新没有三个子阶段的副作用
}
每个mask由与该阶段相关的副作用flags组合而成, 如BeforeMutationMask
由update和snapshot组成. MutationMask
包含的大多是与ui相关的副作用, 所以ui操作发生在此阶段. 当阶段完成时, 根据双缓存机制, 执行root.current = finishedWork
完成current fiber tree
的切换. 当Layout
阶段完成(或跳过)时, 又会开启新的调度(1. commit阶段触发了新的更新, 比如在useLayoutEffect
回调中触发更新; 2. 有遗留的更新未处理).
render阶段的completeWork会完成自下而上的subtreeFlags标记过程
commit三个子阶段会完成自下而上的subtreeFlags消费过程, 即commitXXXEffects/_begin/_complete
三段式. 子阶段的遍历以DFS的顺序从HostRootFiber开始向下遍历到第一个满足条件的fiberNode, 再从其向上遍历直到HostRootFiber为止, 遍历过程中会执行“flags对应操作”.
错误处理
静态方法getDerivedStateFromError
当错误发生后, 提供一个机会渲染 fallback UI.
组件实例方法componentDidCatch
当错误发生后, 提供一个机会记录错误信息.
发生错误时被捕获并交由handleError--render阶段
或captureCommitPhaseError--commit阶段
处理, 如果存在错误边界便执行对应方法, 抛出错误信息(没有则抛出未捕获的错误).
this.setState(() => {
// ...
}, () => {
// ... 用于执行 componentDidCatch的callback
// 及抛出react提示信息的callback
})
ReactDOM.render(element, container, () => {
// ... 用于抛出未捕获的错误及react的提示信息的callback
})
BeforeMutation阶段
此阶段主要工作发生在complete
中的commitBeforeMutationEffectsOnFiber
方法, 处理两种类型的fiberNode.
function commitBeforeMutationEffectsOnFiber(finishedWork){
const current = finishedWork.alternate
const flags = finishedWork.flags
// ...
// 处理包含Snapshot flag的fiberNode
if((flags & Snapshot) !== NoFlags) {
switch(finishedWork.tag){
case ClassComponent: {
if(current !== null) {
const prevProps = current.memoizedProps
const prevState = current.memoizedState
const instance = finishedWork.stateNode
// 1. ClassComponent类型, 执行getSnapshotBeforeUpdate
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
)
}
break;
}
case HostRoot: {
// 2. HostRoot类型, 清空HostRoot挂载的内容, 方便Mutation阶段渲染
if(supportsMutation){
const root = finishedWork.stateNode
clearContainer(root.containerInfo)
}
break
}
}
}
}
Mutation阶段
对于HostComponent
, 主要工作进行DOM元素的增、删、改.
删除DOM元素
Mutation阶段在commitXXXEffects_begin
向下遍历过程中会执行特有的操作, 即删除DOM元素. 要删除的元素fiber.deletions
数组是在render阶段beginWork执行reconcile操作时添加的. 整个删除以DFS顺序遍历子树的每个fiberNode执行对应操作.
执行删除操作的方法是commitDeletion
, 删除一个DOM元素时还需要考虑子树所有组件的unmount逻辑、子树所有ref属性的卸载操作、子树所有Effect相关Hook的destory回调执行等.
插入、移动与更新DOM元素
进入commitXXXEffects_complete
方法, 会对遍历的每个 fiberNode 执行commitMutationEffectsOnFiber
, 在该方法中执行具体的DOM操作.
其中commitPlacement
方法执行插入、移动操作, commitWork
方法执行更新操作. 前者
后者根据fiberNode.updateQueue
(所有变化属性的key、value)改变对应属性(style属性变化、innerHTML、直接文本节点变化、其他元素属性四种类型的数据).
Fiber Tree切换
当前阶段主要工作完成后进入下个阶段之前会进行root.current = finishedWork
切换 Fiber Node.
因为classComponent
执行componentWillUnmount
时(Mutation阶段)Current Fiber Tree 仍对应UI中的树. 执行componentDidmount/Update
时(Layout阶段)Current Fiber Tree 已对应本次更新的Fiber Tree.
Layout阶段
对于 FC , useLayoutEffect callback 会在该阶段执行.(Layout阶段的名称来源于useLayoutEffect
)
Layout阶段在commitXXXEffects_begin
向下遍历过程中也会执行特有的操作(offscreenComponent的显/隐逻辑). 进入commitLayoutMountEffects_complete
方法后会对遍历到的 fiberNode 执行commitLayoutEffectOnFiber
, 根据tag执行不同操作:
- 对于 classComponent 该阶段执行
componentDidmount/Update
方法 - 对于 FC 该阶段执行
useLayoutEffect callback
另: classComponent 中执行this.setState(newState, callback)
传递的 callback 参数、HostRoot 中执行ReactDOM.render(element, container, callback)
传递的 callback 参数都会保存在对应的fiberNode.updateQueue
. 这两种情况下 callback 也会在此中执行commitLayoutEffectOnFiber
.
总结
ReactDOM Renderer的commit
阶段可分为三个阶段:
- 开始前的准备工作, 如判断“是否有副作用需要执行”
- 处理副作用, 包括
BeforeMutation
阶段、Mutation
阶段、Layout
阶段(Fiber Tree的切换会在Mutation完成后, Layout未开始时执行) - 结束后的收尾工作, 比如调度的更新
转载自:https://juejin.cn/post/7228960291303227451