react 源码解析 10.commit 阶段
视频课程(高效学习):进入课程
课程目录:
1.开篇介绍和面试题
2.react的设计理念
3.react源码架构
4.源码目录结构和调试
5.jsx&核心api
6.legacy和concurrent模式入口函数
7.Fiber架构
8.render阶段
9.diff算法
10.commit阶段
11.生命周期
12.状态更新流程
13.hooks源码
14.手写hooks
15.scheduler&Lane
16.concurrent模式
17.context
18事件系统
19.手写迷你版react
20.总结&第一章的面试题解答
21.demo
在 render 阶段的末尾会调用 commitRoot(root);进入 commit 阶段,这里的 root 指的就是 fiberRoot,然后会遍历 render 阶段生成的 effectList,effectList 上的 Fiber 节点保存着对应的 props 变化。之后会遍历 effectList 进行对应的 dom 操作和生命周期、hooks 回调或销毁函数,各个函数做的事情如下
在 commitRoot 函数中其实是调度了 commitRootImpl 函数
//ReactFiberWorkLoop.old.jsfunction commitRoot(root) { var renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel)); return null;}
复制代码
在 commitRootImpl 的函数中主要分三个部分:
//ReactFiberWorkLoop.old.js do { // 调用flushPassiveEffects执行完所有effect的任务 flushPassiveEffects(); } while (rootWithPendingPassiveEffects !== null); //... // 重置变量 finishedWork指rooFiber root.finishedWork = null; //重置优先级 root.finishedLanes = NoLanes; // Scheduler回调函数重置 root.callbackNode = null; root.callbackId = NoLanes; // 重置全局变量 if (root === workInProgressRoot) { workInProgressRoot = null; workInProgress = null; workInProgressRootRenderLanes = NoLanes; } else { } //rootFiber可能会有新的副作用 将它也加入到effectLis let firstEffect; if (finishedWork.effectTag > PerformedWork) { if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { firstEffect = finishedWork.firstEffect; }
复制代码
mutation 阶段
遍历 effectList 分别执行三个方法 commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects 执行对应的 dom 操作和生命周期
在介绍双缓存 Fiber 树的时候,我们在构建完 workInProgress Fiber 树之后会将 fiberRoot 的 current 指向 workInProgress Fiber,让 workInProgress Fiber 成为 current,这个步骤发生在 commitMutationEffects 函数执行之后,commitLayoutEffects 之前,因为 componentWillUnmount 发生在 commitMutationEffects 函数中,这时还可以获取之前的 Update,而 componentDidMount和componentDidUpdate 会在 commitLayoutEffects 中执行,这时已经可以获取更新后的真实 dom 了
function commitRootImpl(root, renderPriorityLevel) { //... do { //... commitBeforeMutationEffects(); } while (nextEffect !== null); do { //... commitMutationEffects(root, renderPriorityLevel);//commitMutationEffects } while (nextEffect !== null); root.current = finishedWork;//切换current Fiber树 do { //... commitLayoutEffects(root, lanes);//commitLayoutEffects } while (nextEffect !== null); //... }
复制代码
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects; // 根据rootDoesHavePassiveEffects赋值相关变量 if (rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsRenderPriority = renderPriorityLevel; } else {} //... // 确保被调度 ensureRootIsScheduled(root, now()); // ... // 执行flushSyncCallbackQueue处理componentDidMount等生命周期或者useLayoutEffect等同步任务 flushSyncCallbackQueue(); return null;
复制代码
现在让我们来看看 mutation 阶段的三个函数分别做了什么事情
commitBeforeMutationEffects 该函数主要做了如下两件事
执行 getSnapshotBeforeUpdate 在源码中 commitBeforeMutationEffectOnFiber 对应的函数是 commitBeforeMutationLifeCycles 在该函数中会调用 getSnapshotBeforeUpdate,现在我们知道了 getSnapshotBeforeUpdate 是在 mutation 阶段中的 commitBeforeMutationEffect 函数中执行的,而 commit 阶段是同步的,所以 getSnapshotBeforeUpdate 也同步执行
function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber, ): void { switch (finishedWork.tag) { //... case ClassComponent: { if const instance = finishedWork.stateNode; const snapshot = instance.getSnapshotBeforeUpdate(//getSnapshotBeforeUpdate finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); } }
复制代码
调度 useEffect
在 flushPassiveEffects 函数中调用 flushPassiveEffectsImpl 遍历 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount,执行对应的 effect 回调和销毁函数,而这两个数组是在 commitLayoutEffects 函数中赋值的(待会就会讲到),mutation 后 effectList 赋值给 rootWithPendingPassiveEffects,然后 scheduleCallback 调度执行 flushPassiveEffects
function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) {//在mutation后变成了root return false; } const unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = [];//useEffect的回调函数 for (let i = 0; i < unmountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); //... const destroy = effect.destroy; destroy(); } const mountEffects = pendingPassiveHookEffectsMount;//useEffect的销毁函数 pendingPassiveHookEffectsMount = []; for (let i = 0; i < mountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); //... const create = effect.create; effect.destroy = create(); } }
复制代码
componentDidUpdate 或 componentDidMount 会在 commit 阶段同步执行(这个后面会讲到),而 useEffect 会在 commit 阶段异步调度,所以适用于数据请求等副作用的处理
注意,和在render阶段的fiber node会打上Placement等标签一样,useEffect或useLayoutEffect也有对应的effect Tag,在源码中对应 export const Passive = /* */ 0b0000000001000000000;
复制代码
function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate; const effectTag = nextEffect.effectTag; // 在commitBeforeMutationEffectOnFiber函数中会执行getSnapshotBeforeUpdate if ((effectTag & Snapshot) !== NoEffect) { commitBeforeMutationEffectOnFiber(current, nextEffect); } // scheduleCallback调度useEffect if ((effectTag & Passive) !== NoEffect) { if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); } } nextEffect = nextEffect.nextEffect;//遍历effectList } }
复制代码
commitMutationEffectscommitMutationEffects 主要做了如下几件事
调用 commitDetachRef 解绑 ref(第 11 章 hook 会讲解)
根据 effectTag 执行对应的 dom 操作
useLayoutEffect 销毁函数在 UpdateTag 时执行
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { //遍历effectList while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 调用commitDetachRef解绑ref if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } } // 根据effectTag执行对应的dom操作 const primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { // 插入dom case Placement: { commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; break; } // 插入更新dom case PlacementAndUpdate: { // 插入 commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } //... // 更新dom case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // 删除dom case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } nextEffect = nextEffect.nextEffect; } }
复制代码
现在让我们来看看操作 dom 的这几个函数
commitPlacement 插入节点:
简化后的代码很清晰,找到该节点最近的 parent 节点和兄弟节点,然后根据 isContainer 来判断是插入到兄弟节点前还是 append 到 parent 节点后
unction commitPlacement(finishedWork: Fiber): void { //... const parentFiber = getHostParentFiber(finishedWork);//找到最近的parent let parent; let isContainer; const parentStateNode = parentFiber.stateNode; switch (parentFiber.tag) { case HostComponent: parent = parentStateNode; isContainer = false; break; //... } const before = getHostSibling(finishedWork);//找兄弟节点 if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); } }
复制代码
commitWork 更新节点:
在简化后的源码中可以看到
如果 fiber 的 tag 是 SimpleMemoComponent 会调用 commitHookEffectListUnmount 执行对应的 hook 的销毁函数,可以看到传入的参数是 HookLayout | HookHasEffect,也就是说执行 useLayoutEffect 的销毁函数。
如果是 HostComponent,那么调用 commitUpdate,commitUpdate 最后会调用 updateDOMProperties 处理对应 Update 的 dom 操作
function commitWork(current: Fiber | null, finishedWork: Fiber): void { if (!supportsMutation) { switch (finishedWork.tag) { //... case SimpleMemoComponent: { commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork); } //... } } switch (finishedWork.tag) { //... case HostComponent: { //... commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); } return; } }
复制代码
function updateDOMProperties( domElement: Element, updatePayload: Array<any>, wasCustomComponentTag: boolean, isCustomComponentTag: boolean, ): void { // TODO: Handle wasCustomComponentTag for (let i = 0; i < updatePayload.length; i += 2) { const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; if (propKey === STYLE) { setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { setTextContent(domElement, propValue); } else { setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } } }
复制代码
commitDeletion 删除节点:
如果是 ClassComponent 会执行 componentWillUnmount,删除 fiber,如果是 FunctionComponent 会删除 ref、并执行 useEffect 的销毁函数,具体可在源码中查看 unmountHostComponents、commitNestedUnmounts、detachFiberMutation 这几个函数
function commitDeletion( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel, ): void { if (supportsMutation) { // Recursively delete all host nodes from the parent. // Detach refs and call componentWillUnmount() on the whole subtree. unmountHostComponents(finishedRoot, current, renderPriorityLevel); } else { // Detach refs and call componentWillUnmount() on the whole subtree. commitNestedUnmounts(finishedRoot, current, renderPriorityLevel); } const alternate = current.alternate; detachFiberMutation(current); if (alternate !== null) { detachFiberMutation(alternate); } }
复制代码
commitLayoutEffects 在 commitMutationEffects 之后所有的 dom 操作都已经完成,可以访问 dom 了,commitLayoutEffects 主要做了
调用 commitLayoutEffectOnFiber 执行相关生命周期函数或者 hook 相关 callback
执行 commitAttachRef 为 ref 赋值
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // 调用commitLayoutEffectOnFiber执行生命周期和hook if (effectTag & (Update | Callback)) { const current = nextEffect.alternate; commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } // ref赋值 if (effectTag & Ref) { commitAttachRef(nextEffect); } nextEffect = nextEffect.nextEffect; } }
复制代码
commitLayoutEffectOnFiber:
在源码中 commitLayoutEffectOnFiber 函数的别名是 commitLifeCycles,在简化后的代码中可以看到,commitLifeCycles 会判断 fiber 的类型,SimpleMemoComponent 会执行 useLayoutEffect 的回调,然后调度 useEffect,ClassComponent 会执行 componentDidMount 或者 componentDidUpdate,this.setState 第二个参数也会执行,HostRoot 会执行 ReactDOM.render 函数的第三个参数,例如
ReactDOM.render(<App />, document.querySelector("#root"), function() { console.log("root mount"); });
复制代码
现在可以知道 useLayoutEffect 是在 commit 阶段同步执行,useEffect 会在 commit 阶段异步调度
function commitLifeCycles( finishedRoot: FiberRoot, current: Fiber | null, finishedWork: Fiber, committedLanes: Lanes, ): void { switch (finishedWork.tag) { case SimpleMemoComponent: { // 此函数会调用useLayoutEffect的回调 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); // 向pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中push effect // 并且调度它们 schedulePassiveEffects(finishedWork); } case ClassComponent: { //条件判断... instance.componentDidMount(); //条件判断... instance.componentDidUpdate(//update 在layout期间同步执行 prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); } case HostRoot: { commitUpdateQueue(finishedWork, updateQueue, instance);//render第三个参数 } } }
复制代码
在 schedulePassiveEffects 中会将 useEffect 的销毁和回调函数 push 到 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount 中
function schedulePassiveEffects(finishedWork: Fiber) { const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; let effect = firstEffect; do { const {next, tag} = effect; if ( (tag & HookPassive) !== NoHookEffect && (tag & HookHasEffect) !== NoHookEffect ) { //push useEffect的销毁函数并且加入调度 enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); //push useEffect的回调函数并且加入调度 enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect !== firstEffect); } }
复制代码
commitAttachRef:
commitAttacRef 中会判断 ref 的类型,执行 ref 或者给 ref.current 赋值
function commitAttachRef(finishedWork: Fiber) { const ref = finishedWork.ref; if (ref !== null) { const instance = finishedWork.stateNode; let instanceToUse; switch (finishedWork.tag) { case HostComponent: instanceToUse = getPublicInstance(instance); break; default: instanceToUse = instance; } if (typeof ref === "function") { // 执行ref回调 ref(instanceToUse); } else { // 如果是值的类型则赋值给ref.current ref.current = instanceToUse; } } }
复制代码
评论