likes
comments
collection
share

React设计原理-commit阶段

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

renderer(最常用的renderer--ReactDOM)工作的阶段被称为commit阶段, 将各种副作用commit到宿主环境UI中. render(reconciler工作的阶段)阶段流程可能被打断, 而commit阶段开始后会同步执行至完毕. 整个阶段分为BeforeMutationMutationLayout三个子阶段. React设计原理-commit阶段

流程概览

开始于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执行不同操作:

  1. 对于 classComponent 该阶段执行componentDidmount/Update方法
  2. 对于 FC 该阶段执行useLayoutEffect callback

另: classComponent 中执行this.setState(newState, callback)传递的 callback 参数、HostRoot 中执行ReactDOM.render(element, container, callback)传递的 callback 参数都会保存在对应的fiberNode.updateQueue. 这两种情况下 callback 也会在此中执行commitLayoutEffectOnFiber.

总结

ReactDOM Renderercommit阶段可分为三个阶段:

  1. 开始前的准备工作, 如判断“是否有副作用需要执行”
  2. 处理副作用, 包括BeforeMutation阶段、Mutation阶段、Layout阶段(Fiber Tree的切换会在Mutation完成后, Layout未开始时执行)
  3. 结束后的收尾工作, 比如调度的更新
转载自:https://juejin.cn/post/7228960291303227451
评论
请登录