写点什么

React 源码分析 5-commit

作者:goClient1992
  • 2022-11-30
    浙江
  • 本文字数:14871 字

    阅读完需:约 49 分钟

前两章讲到了,react 在 render 阶段的 completeUnitWork 执行完毕后,就执行 commitRoot 进入到了 commit 阶段,本章将讲解 commit 阶段执行过程源码。

总览

commit 阶段相比于 render 阶段要简单很多,因为大部分更新的前期操作都在 render 阶段做好了,commit 阶段主要做的是根据之前生成的 effectList,对相应的真实 dom 进行更新和渲染,这个阶段是不可中断的。


commit 阶段大致可以分为以下几个过程:


  1. 获取 effectList 链表,如果 root 上有 effect,则将其也添加进 effectList 中

  2. 对 effectList 进行第一次遍历,执行 commitBeforeMutationEffects 函数来更新 class 组件实例上的 state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数

  3. 对 effectList 进行第二次遍历,执行 commitMutationEffects 函数来完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。

  4. 对 effectList 进行第三次遍历,执行 commitLayoutEffects 函数,去触发 componentDidMount、componentDidUpdate 以及各种回调函数等

  5. 最后进行一点变量还原之类的收尾,就完成了 commit 阶段


我们从 commit 阶段的入口函数 commitRoot 开始看:


// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority( ImmediateSchedulerPriority, commitRootImpl.bind(null, root, renderPriorityLevel), ); return null;}
复制代码


它调用了 commitRootImpl 函数,所要做的工作都在这个函数中:


// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRootImpl(root, renderPriorityLevel) { // ... const finishedWork = root.finishedWork; const lanes = root.finishedLanes; // ...
// 获取 effectList 链表 let firstEffect; if (finishedWork.flags > PerformedWork) { // 如果 root 上有 effect,则将其添加进 effectList 链表中 if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 如果 root 上没有 effect,直接使用 finishedWork.firstEffect 作用链表头节点 firstEffect = finishedWork.firstEffect; }
if (firstEffect !== null) { // ...
// 第一次遍历,执行 commitBeforeMutationEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback(null, commitBeforeMutationEffects, null); // ... } else { try { commitBeforeMutationEffects(); } catch (error) { // ... } } } while (nextEffect !== null);
// ... // 第二次遍历,执行 commitMutationEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback( null, commitMutationEffects, null, root, renderPriorityLevel, ); // ... } else { try { commitMutationEffects(root, renderPriorityLevel); } catch (error) { // ... } } } while (nextEffect !== null);


// 第三次遍历,执行 commitLayoutEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes); // ... } else { try { commitLayoutEffects(root, lanes); } catch (error) { // ... } } } while (nextEffect !== null);
nextEffect = null;
// ... } else { // 没有任何副作用 root.current = finishedWork; if (enableProfilerTimer) { recordCommitTime(); } }
// ...}
复制代码

commitBeforeMutationEffects

commitBeforeMutationEffects 中,会从 firstEffect 开始,通过 nextEffect 不断对 effectList 链表进行遍历,若是当前的 fiber 节点有 flags 副作用,则执行 commitBeforeMutationEffectOnFiber 节点去对针对 class 组件单独处理。


// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitBeforeMutationEffects() { while (nextEffect !== null) { // ... const flags = nextEffect.flags; if ((flags & Snapshot) !== NoFlags) { // 如果当前 fiber 节点有 flags 副作用 commitBeforeMutationEffectOnFiber(current, nextEffect); // ... } // ... nextEffect = nextEffect.nextEffect; }}
复制代码


然后看一下 commitBeforeMutationEffectOnFiber,它里面根据 fiber 的 tag 属性,主要是对 ClassComponent 组件进行处理,更新 ClassComponent 实例上的 state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber,): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { return; } case ClassComponent: { if (finishedWork.flags & Snapshot) { if (current !== null) { // 非首次加载的情况下 // 获取上一次的 props 和 state const prevProps = current.memoizedProps; const prevState = current.memoizedState; // 获取当前 class 组件实例 const instance = finishedWork.stateNode; // ...
// 调用 getSnapshotBeforeUpdate 生命周期方法 const snapshot = instance.getSnapshotBeforeUpdate( finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); // ... // 将生成的 snapshot 保存到 instance.__reactInternalSnapshotBeforeUpdate 上,供 DidUpdate 生命周期使用 instance.__reactInternalSnapshotBeforeUpdate = snapshot; } } return; } // ... }}
复制代码

commitMutationEffects

commitMutationEffects 中会根据对 effectList 进行第二次遍历,根据 flags 的类型进行二进制与操作,然后根据结果去执行不同的操作,对真实 dom 进行修改:


  • ContentReset: 如果 flags 中包含 ContentReset 类型,代表文本节点内容改变,则执行 commitResetTextContent 重置文本节点的内容

  • Ref: 如果 flags 中包含 Ref 类型,则执行 commitDetachRef 更改 ref 对应的 current 的值

  • Placement: 上一章 diff 中讲过 Placement 代表插入,会执行 commitPlacement 去插入 dom 节点

  • Update: flags 包含 Update 则会执行 commitWork 执行更新操作

  • Deletion: flags 包含 Deletion 则会执行 commitDeletion 执行更新操作


// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitMutationEffects( root: FiberRoot, renderPriorityLevel: ReactPriorityLevel,) { // 对 effectList 进行遍历 while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
// ContentReset:重置文本节点 if (flags & ContentReset) { commitResetTextContent(nextEffect); }
// Ref:commitDetachRef 更新 ref 的 current 值 if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } if (enableScopeAPI) { if (nextEffect.tag === ScopeComponent) { commitAttachRef(nextEffect); } } }
// 执行更新、插入、删除操作 const primaryFlags = flags & (Placement | Update | Deletion | Hydrating); switch (primaryFlags) { case Placement: { // 插入 commitPlacement(nextEffect); nextEffect.flags &= ~Placement; break; } case PlacementAndUpdate: { // 插入并更新 // 插入 commitPlacement(nextEffect); nextEffect.flags &= ~Placement;
// 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // ... case Update: { // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { // 删除 commitDeletion(root, nextEffect, renderPriorityLevel); break; } } resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; }}
复制代码


相关参考视频讲解:进入学习


下面我们重点来看一下 react 是如何对真实 dom 节点进行操作的。

插入 dom 节点

获取父节点及插入位置

插入 dom 节点的操作以 commitPlacement 为入口函数, commitPlacement 中会首先获取当前 fiber 的父 fiber 对应的真实 dom 节点以及在父节点下要插入的位置,根据父节点对应的 dom 是否为 container,去执行 insertOrAppendPlacementNodeIntoContainer 或者 insertOrAppendPlacementNode 进行节点的插入。


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitPlacement(finishedWork: Fiber): void { if (!supportsMutation) { return; }
// 获取当前 fiber 的父 fiber const parentFiber = getHostParentFiber(finishedWork);
let parent; let isContainer; // 获取父 fiber 对应真实 dom 节点 const parentStateNode = parentFiber.stateNode; // 获取父 fiber 对应的 dom 是否可以作为 container case HostComponent: parent = parentStateNode; isContainer = false; break; case HostRoot: parent = parentStateNode.containerInfo; isContainer = true; break; case HostPortal: parent = parentStateNode.containerInfo; isContainer = true; break; case FundamentalComponent: if (enableFundamentalAPI) { parent = parentStateNode.instance; isContainer = false; } default: invariant( false, 'Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.', ); } // 如果父 fiber 有 ContentReset 的 flags 副作用,则重置其文本内容 if (parentFiber.flags & ContentReset) { resetTextContent(parent); parentFiber.flags &= ~ContentReset; }
// 获取要在哪个兄弟 fiber 之前插入 const before = getHostSibling(finishedWork); if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); }}
复制代码

判断当前节点是否为单节点

我们以 insertOrAppendPlacementNodeIntoContainer 为例看一下其源码,里面通过 tag 属性判断了当前的 fiber 是否为原生 dom 节点。若是,则调用 insertInContainerBeforeappendChildToContainer 在相应位置插入真实 dom;若不是,则对当前 fiber 的所有子 fiber 调用 insertOrAppendPlacementNodeIntoContainer 进行遍历:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function insertOrAppendPlacementNodeIntoContainer( node: Fiber, before: ?Instance, parent: Container,): void { const {tag} = node; // 判断当前节点是否为原生的 dom 节点 const isHost = tag === HostComponent || tag === HostText; if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) { // 是原生 dom 节点,在父节点的对应位置插入当前节点 const stateNode = isHost ? node.stateNode : node.stateNode.instance; if (before) { insertInContainerBefore(parent, stateNode, before); } else { appendChildToContainer(parent, stateNode); } } else if (tag === HostPortal) { // 如是 Portal 不做处理 } else { // 不是原生 dom 节点,则遍历插入当前节点的各个子节点 const child = node.child; if (child !== null) { insertOrAppendPlacementNodeIntoContainer(child, before, parent); let sibling = child.sibling; while (sibling !== null) { insertOrAppendPlacementNodeIntoContainer(sibling, before, parent); sibling = sibling.sibling; } } }}
复制代码

在对应位置插入节点

before 不为 null 时,说明要在某个 dom 节点之前插入新的 dom,调用 insertInContainerBefore 去进行插入,根据父节点是否注释类型,选择在父节点的父节点下插入新的 dom,还是直接在父节点下插入新的 dom:


// packages/react-dom/src/client/ReactDOMHostConfig.js
export function insertInContainerBefore( container: Container, child: Instance | TextInstance, beforeChild: Instance | TextInstance | SuspenseInstance,): void { if (container.nodeType === COMMENT_NODE) { // 如果父节点为注释类型,则在父节点的父节点下插入新的 dom (container.parentNode: any).insertBefore(child, beforeChild); } else { // 否则直接插入新的 dom container.insertBefore(child, beforeChild); }}
复制代码


before 为 null 时,调用 appendChildToContainer 方法,直接在父节点(如果父节点为注释类型则在父节点的父节点)的最后位置插入新的 dom:


export function appendChildToContainer(  container: Container,  child: Instance | TextInstance,): void {  let parentNode;  if (container.nodeType === COMMENT_NODE) {    // 如果父节点为注释类型,则在父节点的父节点下插入新的 dom    parentNode = (container.parentNode: any);    parentNode.insertBefore(child, container);  } else {    // 否则直接插入新的 dom    parentNode = container;    parentNode.appendChild(child);  }  // ...}
复制代码


这几步都是以 insertOrAppendPlacementNodeIntoContainer 为例看源码,insertOrAppendPlacementNode 和它的唯一区别就是最后在对应位置插入节点时,不需要额外判断父节点 (container) 是否为 COMMENT_TYPE 了。

更新 dom 节点

更新操作以 commitWork 为入口函数,更新主要是针对 HostComponent 和 HostText 两种类型进行更新。


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitWork(current: Fiber | null, finishedWork: Fiber): void { // ...
switch (finishedWork.tag) { // ... case ClassComponent: { return; } case HostComponent: { // 获取真实 dom 节点 const instance: Instance = finishedWork.stateNode; if (instance != null) { // 获取新的 props const newProps = finishedWork.memoizedProps; // 获取老的 props const oldProps = current !== null ? current.memoizedProps : newProps; const type = finishedWork.type; // 取出 updateQueue const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any); // 清空 updateQueue finishedWork.updateQueue = null; if (updatePayload !== null) { // 提交更新 commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); } } return; } case HostText: { // 获取真实文本节点 const textInstance: TextInstance = finishedWork.stateNode; // 获取新的文本内容 const newText: string = finishedWork.memoizedProps; // 获取老的文本内容 const oldText: string = current !== null ? current.memoizedProps : newText; // 提交更新 commitTextUpdate(textInstance, oldText, newText); return; } case HostRoot: { // ssr操作,暂不关注 if (supportsHydration) { const root: FiberRoot = finishedWork.stateNode; if (root.hydrate) { root.hydrate = false; commitHydratedContainer(root.containerInfo); } } return; } case Profiler: { return; } // ...}
复制代码

更新 HostComponent

根据上面的 commitWork 的源码,更新 HostComponent 时,获取了真实 dom 节点实例、props 以及 updateQueue 之后,就调用 commitUpdate 对 dom 进行更新,它通过 updateProperties 函数将 props 变化应用到真实 dom 上。


// packages/react-dom/src/client/ReactDOMHostConfig.js
export function commitUpdate( domElement: Instance, updatePayload: Array<mixed>, type: string, oldProps: Props, newProps: Props, internalInstanceHandle: Object,): void { // 做了 domElement[internalPropsKey] = props 的操作 updateFiberProps(domElement, newProps); // 应用给真实 dom updateProperties(domElement, updatePayload, type, oldProps, newProps);}
复制代码


updateProperties 中,通过 updateDOMProperties 将 diff 结果应用于真实的 dom 节点。另外根据 fiber 的 tag 属性,如果判断对应的 dom 的节点为表单类型,例如 radio、textarea、input、select 等,会做特定的处理:


// packages/react-dom/src/client/ReactDOMComponent.js
export function updateProperties( domElement: Element, updatePayload: Array<any>, tag: string, lastRawProps: Object, nextRawProps: Object,): void { // 针对表单组件进行特殊处理,例如更新 radio 的 checked 值 if ( tag === 'input' && nextRawProps.type === 'radio' && nextRawProps.name != null ) { ReactDOMInputUpdateChecked(domElement, nextRawProps); }
// 判断是否为用户自定义的组件,即是否包含 "-" const wasCustomComponentTag = isCustomComponent(tag, lastRawProps); const isCustomComponentTag = isCustomComponent(tag, nextRawProps); // 将 diff 结果应用于真实 dom updateDOMProperties( domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag, );
// 针对表单的特殊处理 switch (tag) { case 'input': ReactDOMInputUpdateWrapper(domElement, nextRawProps); break; case 'textarea': ReactDOMTextareaUpdateWrapper(domElement, nextRawProps); break; case 'select': ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps); break; }}
复制代码


updateDOMProperties 中,会遍历之前 render 阶段生成的 updatePayload,将其映射到真实的 dom 节点属性上,另外会针对 style、dangerouslySetInnerHTML 以及 textContent 做一些处理,从而实现了 dom 的更新:


// packages/react-dom/src/client/ReactDOMHostConfig.js
function updateDOMProperties( domElement: Element, updatePayload: Array<any>, wasCustomComponentTag: boolean, isCustomComponentTag: boolean,): void { // 对 updatePayload 遍历 for (let i = 0; i < updatePayload.length; i += 2) { const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; if (propKey === STYLE) { // 处理 style 样式更新 setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { // 处理 innerHTML 改变 setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { // 处理 textContent setTextContent(domElement, propValue); } else { // 处理其他节点属性 setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } }}
复制代码

更新 HostText

HostText 的更新处理十分简单,调用 commitTextUpdate,里面直接将 dom 的 nodeValue 设置为 newText 的值:


// packages/react-dom/src/client/ReactDOMHostConfig.js
export function commitTextUpdate( textInstance: TextInstance, oldText: string, newText: string,): void { textInstance.nodeValue = newText;}
复制代码

删除 dom 节点

删除 dom 节点的操作以 commitDeletion 为入口函数,它所要做的事情最复杂。react 会采用深度优先遍历去遍历整颗 fiber 树,找到需要删除的 fiber,除了要将对应的 dom 节点删除,还需要考虑 ref 的卸载、componentWillUnmount 等生命周期的调用:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitDeletion( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel,): void { if (supportsMutation) { // 支持 useMutation unmountHostComponents(finishedRoot, current, renderPriorityLevel); } else { // 不支持 useMutation commitNestedUnmounts(finishedRoot, current, renderPriorityLevel); } const alternate = current.alternate; // 重置 fiber 的各项属性 detachFiberMutation(current); if (alternate !== null) { detachFiberMutation(alternate); }}
复制代码

unmountHostComponents

unmountHostComponents 首先判断当前父节点是否合法,若是不合法寻找合法的父节点,然后通过深度优先遍历,去遍历整棵树,通过 commitUnmount 卸载 ref、执行生命周期。遇到是原生 dom 类型的节点,还会从对应的父节点下删除该节点。


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function unmountHostComponents( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel,): void { let node: Fiber = current; let currentParentIsValid = false; let currentParent; let currentParentIsContainer;
while (true) { if (!currentParentIsValid) { // 若当前的父节点不是非法的 dom 节点,寻找一个合法的 dom 父节点 let parent = node.return; findParent: while (true) { invariant( parent !== null, 'Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.', ); const parentStateNode = parent.stateNode; switch (parent.tag) { case HostComponent: currentParent = parentStateNode; currentParentIsContainer = false; break findParent; case HostRoot: currentParent = parentStateNode.containerInfo; currentParentIsContainer = true; break findParent; case HostPortal: currentParent = parentStateNode.containerInfo; currentParentIsContainer = true; break findParent; case FundamentalComponent: if (enableFundamentalAPI) { currentParent = parentStateNode.instance; currentParentIsContainer = false; } } parent = parent.return; } currentParentIsValid = true; }
if (node.tag === HostComponent || node.tag === HostText) { // 若果是原生 dom 节点,调用 commitNestedUnmounts 方法 commitNestedUnmounts(finishedRoot, node, renderPriorityLevel); if (currentParentIsContainer) { // 若当前的 parent 是 container,则将 child 从 container 中移除(通过 dom.removeChild 方法) removeChildFromContainer( ((currentParent: any): Container), (node.stateNode: Instance | TextInstance), ); } else { // 从 parent 中移除 child(通过 dom.removeChild 方法) removeChild( ((currentParent: any): Instance), (node.stateNode: Instance | TextInstance), ); } } // ... else if (node.tag === HostPortal) { // 若是 portal 节点,直接向下遍历 child,因为它没有 ref 和生命周期等额外要处理的事情 if (node.child !== null) { currentParent = node.stateNode.containerInfo; currentParentIsContainer = true; node.child.return = node; node = node.child; continue; } } else { // 其他 react 节点,调用 commitUnmount,里面会卸载 ref、执行生命周期等 commitUnmount(finishedRoot, node, renderPriorityLevel); // 深度优先遍历子节点 if (node.child !== null) { node.child.return = node; node = node.child; continue; } } // node 和 current 相等时说明整颗树的深度优先遍历完成 if (node === current) { return; } // 如果没有兄弟节点,说明当前子树遍历完毕,返回到父节点继续深度优先遍历 while (node.sibling === null) { if (node.return === null || node.return === current) { return; } node = node.return; if (node.tag === HostPortal) { currentParentIsValid = false; } } // 继续遍历兄弟节点 node.sibling.return = node.return; node = node.sibling; }}
复制代码

commitNestedUnmounts

commitNestedUnmounts 相比 unmountHostComponents 不需要额外做当前父节点是否合法的判断以及 react 节点类型的判断,直接采用深度优先遍历,去执行 commitUnmount 方法即可:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitNestedUnmounts( finishedRoot: FiberRoot, root: Fiber, renderPriorityLevel: ReactPriorityLevel,): void { let node: Fiber = root; while (true) { // 调用 commitUnmount 去卸载 ref、执行生命周期 commitUnmount(finishedRoot, node, renderPriorityLevel); if ( node.child !== null && (!supportsMutation || node.tag !== HostPortal) ) { // 深度优先遍历向下遍历子树 node.child.return = node; node = node.child; continue; } if (node === root) { // node 为 root 时说明整棵树的深度优先遍历完成 return; } while (node.sibling === null) { // node.sibling 为 null 时说明当前子树遍历完成,返回上级节点继续深度优先遍历 if (node.return === null || node.return === root) { return; } node = node.return; } // 遍历兄弟节点 node.sibling.return = node.return; node = node.sibling; }}
复制代码

commitUnmount

commitUnmount 中会完成对 react 组件 ref 的卸载,若果是类组件,执行 componentWillUnmount 生命周期等操作:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitUnmount( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel,): void { onCommitUnmount(current);
switch (current.tag) { case FunctionComponent: case ForwardRef: case MemoComponent: case SimpleMemoComponent: // ... case ClassComponent: { // 卸载 ref safelyDetachRef(current); const instance = current.stateNode; // 执行 componentWillUnmount 生命周期 if (typeof instance.componentWillUnmount === 'function') { safelyCallComponentWillUnmount(current, instance); } return; } case HostComponent: { // 卸载 ref safelyDetachRef(current); return; } case HostPortal: { if (supportsMutation) { // 递归遍历子树 unmountHostComponents(finishedRoot, current, renderPriorityLevel); } else if (supportsPersistence) { emptyPortalContainer(current); } return; } // ... }}
复制代码


最终通过以上操作,react 就完成了 dom 的删除工作。

commitLayoutEffects

接下来通过 commitLayoutEffects 为入口函数,执行第三次遍历,这里会遍历 effectList,执行 componentDidMountcomponentDidUpdate 等生命周期,另外会执行 componentUpdateQueue 函数去执行回调函数。


// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { // ...
// 遍历 effectList while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
if (flags & (Update | Callback)) { const current = nextEffect.alternate; // 执行 componentDidMount、componentDidUpdate 以及 componentUpdateQueue commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); }
// 更新 ref if (enableScopeAPI) { if (flags & Ref && nextEffect.tag !== ScopeComponent) { commitAttachRef(nextEffect); } } else { if (flags & Ref) { commitAttachRef(nextEffect); } }
resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; }}
复制代码

执行生命周期

commitLayoutEffectOnFiber 调用了 packages/react-reconciler/src/ReactFiberCommitWork.old.js 路径下的 commitLifeCycles 函数,里面针对首次渲染和非首次渲染分别执行 componentDidMountcomponentDidUpdate 生命周期,以及调用 commitUpdateQueue 去触发回调:


// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitLifeCycles( finishedRoot: FiberRoot, current: Fiber | null, finishedWork: Fiber, committedLanes: Lanes,): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: // ... case ClassComponent: { const instance = finishedWork.stateNode; if (finishedWork.flags & Update) { if (current === null) { // 首次渲染,执行 componentDidMount 生命周期 if ( enableProfilerTimer && enableProfilerCommitHooks && finishedWork.mode & ProfileMode ) { try { startLayoutEffectTimer(); instance.componentDidMount(); } finally { recordLayoutEffectDuration(finishedWork); } } else { instance.componentDidMount(); } } else { // 非首次渲染,执行 componentDidUpdate 生命周期 const prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps); const prevState = current.memoizedState; // ... if ( enableProfilerTimer && enableProfilerCommitHooks && finishedWork.mode & ProfileMode ) { try { startLayoutEffectTimer(); instance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); } finally { recordLayoutEffectDuration(finishedWork); } } else { instance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); } } }
// ...
if (updateQueue !== null) { // ... // 执行 commitUpdateQueue 处理回调 commitUpdateQueue(finishedWork, updateQueue, instance); } return; } case HostRoot: {
const updateQueue: UpdateQueue< *, > | null = (finishedWork.updateQueue: any); if (updateQueue !== null) { // ... // 调用 commitUpdateQueue 处理 ReactDOM.render 的回调 commitUpdateQueue(finishedWork, updateQueue, instance); } return; } case HostComponent: { const instance: Instance = finishedWork.stateNode; // ... // commitMount 处理 input 标签有 auto-focus 的情况 if (current === null && finishedWork.flags & Update) { const type = finishedWork.type; const props = finishedWork.memoizedProps; commitMount(instance, type, props, finishedWork); }
return; } // ...}
复制代码

处理回调

处理回调是在 commitUpdateQueue 中做的,它会对 finishedQueue 上面的 effects 进行遍历,若有 callback,则执行 callback。同时会重置 finishedQueue 上面的 effects 为 null:


// packages/react-reconciler/src/ReactUpdateQueue.old.js
export function commitUpdateQueue<State>( finishedWork: Fiber, finishedQueue: UpdateQueue<State>, instance: any,): void { const effects = finishedQueue.effects; // 清空 effects finishedQueue.effects = null; // 对 effect 遍历 if (effects !== null) { for (let i = 0; i < effects.length; i++) { const effect = effects[i]; const callback = effect.callback; // 执行回调 if (callback !== null) { effect.callback = null; callCallback(callback, instance); } } }}
复制代码


在这之后就是进行最后一点变量还原等收尾工作,然后整个 commit 过程就完成了!

总结

接着第(4)章 render 阶段的流程图,补充上 commit 阶段的流程图,就构成了完整的 react 执行图了:



用户头像

goClient1992

关注

还未添加个人签名 2021-12-03 加入

还未添加个人简介

评论

发布
暂无评论
React源码分析5-commit_React_goClient1992_InfoQ写作社区