React commit源碼分析詳解
總覽
commit 階段相比于 render 階段要簡單很多,因為大部分更新的前期操作都在 render 階段做好了,commit 階段主要做的是根據(jù)之前生成的 effectList,對相應(yīng)的真實 dom 進行更新和渲染,這個階段是不可中斷的。
commit 階段大致可以分為以下幾個過程:
- 獲取 effectList 鏈表,如果 root 上有 effect,則將其也添加進 effectList 中
- 對 effectList 進行第一次遍歷,執(zhí)行
commitBeforeMutationEffects函數(shù)來更新class組件實例上的state、props 等,以及執(zhí)行 getSnapshotBeforeUpdate 生命周期函數(shù) - 對 effectList 進行第二次遍歷,執(zhí)行
commitMutationEffects函數(shù)來完成副作用的執(zhí)行,主要包括重置文本節(jié)點以及真實 dom 節(jié)點的插入、刪除和更新等操作。 - 對 effectList 進行第三次遍歷,執(zhí)行
commitLayoutEffects函數(shù),去觸發(fā) componentDidMount、componentDidUpdate 以及各種回調(diào)函數(shù)等 - 最后進行一點變量還原之類的收尾,就完成了 commit 階段
我們從 commit 階段的入口函數(shù) commitRoot 開始看:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediateSchedulerPriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}它調(diào)用了 commitRootImpl 函數(shù),所要做的工作都在這個函數(shù)中:
// 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 作用鏈表頭節(jié)點
firstEffect = finishedWork.firstEffect;
}
if (firstEffect !== null) {
// ...
// 第一次遍歷,執(zhí)行 commitBeforeMutationEffects
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
// ...
} else {
try {
commitBeforeMutationEffects();
} catch (error) {
// ...
}
}
} while (nextEffect !== null);
// ...
// 第二次遍歷,執(zhí)行 commitMutationEffects
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitMutationEffects,
null,
root,
renderPriorityLevel,
);
// ...
} else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
// ...
}
}
} while (nextEffect !== null);
// 第三次遍歷,執(zhí)行 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 鏈表進行遍歷,若是當(dāng)前的 fiber 節(jié)點有 flags 副作用,則執(zhí)行 commitBeforeMutationEffectOnFiber 節(jié)點去對針對 class 組件單獨處理。
相關(guān)參考視頻講解:傳送門
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
// ...
const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
// 如果當(dāng)前 fiber 節(jié)點有 flags 副作用
commitBeforeMutationEffectOnFiber(current, nextEffect);
// ...
}
// ...
nextEffect = nextEffect.nextEffect;
}
}
然后看一下 commitBeforeMutationEffectOnFiber,它里面根據(jù) fiber 的 tag 屬性,主要是對 ClassComponent 組件進行處理,更新 ClassComponent 實例上的state、props 等,以及執(zhí)行 getSnapshotBeforeUpdate 生命周期函數(shù):
// 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;
// 獲取當(dāng)前 class 組件實例
const instance = finishedWork.stateNode;
// ...
// 調(diào)用 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 中會根據(jù)對 effectList 進行第二次遍歷,根據(jù) flags 的類型進行二進制與操作,然后根據(jù)結(jié)果去執(zhí)行不同的操作,對真實 dom 進行修改:相關(guān)參考視頻講解:進入學(xué)習(xí)
- ContentReset: 如果 flags 中包含 ContentReset 類型,代表文本節(jié)點內(nèi)容改變,則執(zhí)行
commitResetTextContent重置文本節(jié)點的內(nèi)容 - Ref: 如果 flags 中包含 Ref 類型,則執(zhí)行
commitDetachRef更改 ref 對應(yīng)的 current 的值 - Placement: 上一章 diff 中講過 Placement 代表插入,會執(zhí)行
commitPlacement去插入 dom 節(jié)點 - Update: flags 包含 Update 則會執(zhí)行
commitWork執(zhí)行更新操作 - Deletion: flags 包含 Deletion 則會執(zhí)行
commitDeletion執(zhí)行更新操作
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitMutationEffects(
root: FiberRoot, renderPriorityLevel: ReactPriorityLevel,
) {
// 對 effectList 進行遍歷
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
// ContentReset:重置文本節(jié)點
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);
}
}
}
// 執(zhí)行更新、插入、刪除操作
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 節(jié)點進行操作的。
插入 dom 節(jié)點
獲取父節(jié)點及插入位置
插入 dom 節(jié)點的操作以 commitPlacement 為入口函數(shù), commitPlacement 中會首先獲取當(dāng)前 fiber 的父 fiber 對應(yīng)的真實 dom 節(jié)點以及在父節(jié)點下要插入的位置,根據(jù)父節(jié)點對應(yīng)的 dom 是否為 container,去執(zhí)行 insertOrAppendPlacementNodeIntoContainer 或者 insertOrAppendPlacementNode 進行節(jié)點的插入。
// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
// 獲取當(dāng)前 fiber 的父 fiber
const parentFiber = getHostParentFiber(finishedWork);
let parent;
let isContainer;
// 獲取父 fiber 對應(yīng)真實 dom 節(jié)點
const parentStateNode = parentFiber.stateNode;
// 獲取父 fiber 對應(yīng)的 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 副作用,則重置其文本內(nèi)容
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);
}
}判斷當(dāng)前節(jié)點是否為單節(jié)點
我們以 insertOrAppendPlacementNodeIntoContainer 為例看一下其源碼,里面通過 tag 屬性判斷了當(dāng)前的 fiber 是否為原生 dom 節(jié)點。若是,則調(diào)用 insertInContainerBefore 或 appendChildToContainer 在相應(yīng)位置插入真實 dom;若不是,則對當(dāng)前 fiber 的所有子 fiber 調(diào)用 insertOrAppendPlacementNodeIntoContainer 進行遍歷:
// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function insertOrAppendPlacementNodeIntoContainer(
node: Fiber, before: ?Instance, parent: Container,
): void {
const {tag} = node;
// 判斷當(dāng)前節(jié)點是否為原生的 dom 節(jié)點
const isHost = tag === HostComponent || tag === HostText;
if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
// 是原生 dom 節(jié)點,在父節(jié)點的對應(yīng)位置插入當(dāng)前節(jié)點
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 節(jié)點,則遍歷插入當(dāng)前節(jié)點的各個子節(jié)點
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;
}
}
}
}在對應(yīng)位置插入節(jié)點
before 不為 null 時,說明要在某個 dom 節(jié)點之前插入新的 dom,調(diào)用 insertInContainerBefore 去進行插入,根據(jù)父節(jié)點是否注釋類型,選擇在父節(jié)點的父節(jié)點下插入新的 dom,還是直接在父節(jié)點下插入新的 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) {
// 如果父節(jié)點為注釋類型,則在父節(jié)點的父節(jié)點下插入新的 dom
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
// 否則直接插入新的 dom
container.insertBefore(child, beforeChild);
}
}before 為 null 時,調(diào)用 appendChildToContainer 方法,直接在父節(jié)點(如果父節(jié)點為注釋類型則在父節(jié)點的父節(jié)點)的最后位置插入新的 dom:
export function appendChildToContainer(
container: Container, child: Instance | TextInstance,
): void {
let parentNode;
if (container.nodeType === COMMENT_NODE) {
// 如果父節(jié)點為注釋類型,則在父節(jié)點的父節(jié)點下插入新的 dom
parentNode = (container.parentNode: any);
parentNode.insertBefore(child, container);
} else {
// 否則直接插入新的 dom
parentNode = container;
parentNode.appendChild(child);
}
// ...
}這幾步都是以 insertOrAppendPlacementNodeIntoContainer 為例看源碼,insertOrAppendPlacementNode 和它的唯一區(qū)別就是最后在對應(yīng)位置插入節(jié)點時,不需要額外判斷父節(jié)點 (container) 是否為 COMMENT_TYPE 了。
更新 dom 節(jié)點
更新操作以 commitWork 為入口函數(shù),更新主要是針對 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 節(jié)點
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: {
// 獲取真實文本節(jié)點
const textInstance: TextInstance = finishedWork.stateNode;
// 獲取新的文本內(nèi)容
const newText: string = finishedWork.memoizedProps;
// 獲取老的文本內(nèi)容
const oldText: string =
current !== null ? current.memoizedProps : newText;
// 提交更新
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
// ssr操作,暫不關(guān)注
if (supportsHydration) {
const root: FiberRoot = finishedWork.stateNode;
if (root.hydrate) {
root.hydrate = false;
commitHydratedContainer(root.containerInfo);
}
}
return;
}
case Profiler: {
return;
}
// ...
}更新 HostComponent
根據(jù)上面的 commitWork 的源碼,更新 HostComponent 時,獲取了真實 dom 節(jié)點實例、props 以及 updateQueue 之后,就調(diào)用 commitUpdate 對 dom 進行更新,它通過 updateProperties 函數(shù)將 props 變化應(yīng)用到真實 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);
// 應(yīng)用給真實 dom
updateProperties(domElement, updatePayload, type, oldProps, newProps);
}updateProperties 中,通過 updateDOMProperties 將 diff 結(jié)果應(yīng)用于真實的 dom 節(jié)點。另外根據(jù) fiber 的 tag 屬性,如果判斷對應(yīng)的 dom 的節(jié)點為表單類型,例如 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 結(jié)果應(yīng)用于真實 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 節(jié)點屬性上,另外會針對 style、dangerouslySetInnerHTML 以及 textContent 做一些處理,從而實現(xiàn)了 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 {
// 處理其他節(jié)點屬性
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
}
}
}更新 HostText
HostText 的更新處理十分簡單,調(diào)用 commitTextUpdate,里面直接將 dom 的 nodeValue 設(shè)置為 newText 的值:
// packages/react-dom/src/client/ReactDOMHostConfig.js
export function commitTextUpdate(
textInstance: TextInstance, oldText: string, newText: string,
): void {
textInstance.nodeValue = newText;
}
刪除 dom 節(jié)點
刪除 dom 節(jié)點的操作以 commitDeletion 為入口函數(shù),它所要做的事情最復(fù)雜。react 會采用深度優(yōu)先遍歷去遍歷整顆 fiber 樹,找到需要刪除的 fiber,除了要將對應(yīng)的 dom 節(jié)點刪除,還需要考慮 ref 的卸載、componentWillUnmount 等生命周期的調(diào)用:
// 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 首先判斷當(dāng)前父節(jié)點是否合法,若是不合法尋找合法的父節(jié)點,然后通過深度優(yōu)先遍歷,去遍歷整棵樹,通過 commitUnmount 卸載 ref、執(zhí)行生命周期。遇到是原生 dom 類型的節(jié)點,還會從對應(yīng)的父節(jié)點下刪除該節(jié)點。
// 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) {
// 若當(dāng)前的父節(jié)點不是非法的 dom 節(jié)點,尋找一個合法的 dom 父節(jié)點
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 節(jié)點,調(diào)用 commitNestedUnmounts 方法
commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
if (currentParentIsContainer) {
// 若當(dāng)前的 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 節(jié)點,直接向下遍歷 child,因為它沒有 ref 和生命周期等額外要處理的事情
if (node.child !== null) {
currentParent = node.stateNode.containerInfo;
currentParentIsContainer = true;
node.child.return = node;
node = node.child;
continue;
}
} else {
// 其他 react 節(jié)點,調(diào)用 commitUnmount,里面會卸載 ref、執(zhí)行生命周期等
commitUnmount(finishedRoot, node, renderPriorityLevel);
// 深度優(yōu)先遍歷子節(jié)點
if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
}
// node 和 current 相等時說明整顆樹的深度優(yōu)先遍歷完成
if (node === current) {
return;
}
// 如果沒有兄弟節(jié)點,說明當(dāng)前子樹遍歷完畢,返回到父節(jié)點繼續(xù)深度優(yōu)先遍歷
while (node.sibling === null) {
if (node.return === null || node.return === current) {
return;
}
node = node.return;
if (node.tag === HostPortal) {
currentParentIsValid = false;
}
}
// 繼續(xù)遍歷兄弟節(jié)點
node.sibling.return = node.return;
node = node.sibling;
}
}commitNestedUnmounts
commitNestedUnmounts 相比 unmountHostComponents 不需要額外做當(dāng)前父節(jié)點是否合法的判斷以及 react 節(jié)點類型的判斷,直接采用深度優(yōu)先遍歷,去執(zhí)行 commitUnmount 方法即可:
// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitNestedUnmounts(
finishedRoot: FiberRoot, root: Fiber, renderPriorityLevel: ReactPriorityLevel,
): void {
let node: Fiber = root;
while (true) {
// 調(diào)用 commitUnmount 去卸載 ref、執(zhí)行生命周期
commitUnmount(finishedRoot, node, renderPriorityLevel);
if (
node.child !== null &&
(!supportsMutation || node.tag !== HostPortal)
) {
// 深度優(yōu)先遍歷向下遍歷子樹
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
// node 為 root 時說明整棵樹的深度優(yōu)先遍歷完成
return;
}
while (node.sibling === null) {
// node.sibling 為 null 時說明當(dāng)前子樹遍歷完成,返回上級節(jié)點繼續(xù)深度優(yōu)先遍歷
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
// 遍歷兄弟節(jié)點
node.sibling.return = node.return;
node = node.sibling;
}
}commitUnmount
commitUnmount 中會完成對 react 組件 ref 的卸載,若果是類組件,執(zhí)行 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;
// 執(zhí)行 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 為入口函數(shù),執(zhí)行第三次遍歷,這里會遍歷 effectList,執(zhí)行 componentDidMount、componentDidUpdate 等生命周期,另外會執(zhí)行 componentUpdateQueue 函數(shù)去執(zhí)行回調(diào)函數(shù)。
// 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;
// 執(zhí)行 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;
}
}執(zhí)行生命周期
commitLayoutEffectOnFiber 調(diào)用了 packages/react-reconciler/src/ReactFiberCommitWork.old.js 路徑下的 commitLifeCycles 函數(shù),里面針對首次渲染和非首次渲染分別執(zhí)行 componentDidMount 和 componentDidUpdate 生命周期,以及調(diào)用 commitUpdateQueue 去觸發(fā)回調(diào):
// 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) {
// 首次渲染,執(zhí)行 componentDidMount 生命周期
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally {
recordLayoutEffectDuration(finishedWork);
}
} else {
instance.componentDidMount();
}
} else {
// 非首次渲染,執(zhí)行 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) {
// ...
// 執(zhí)行 commitUpdateQueue 處理回調(diào)
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
case HostRoot: {
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if (updateQueue !== null) {
// ...
// 調(diào)用 commitUpdateQueue 處理 ReactDOM.render 的回調(diào)
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
// ...
// commitMount 處理 input 標(biāo)簽有 auto-focus 的情況
if (current === null && finishedWork.flags & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
// ...
}處理回調(diào)
處理回調(diào)是在 commitUpdateQueue 中做的,它會對 finishedQueue 上面的 effects 進行遍歷,若有 callback,則執(zhí)行 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;
// 執(zhí)行回調(diào)
if (callback !== null) {
effect.callback = null;
callCallback(callback, instance);
}
}
}
}在這之后就是進行最后一點變量還原等收尾工作,然后整個 commit 過程就完成了!
總結(jié)
接 render 階段的流程圖,補充上 commit 階段的流程圖,就構(gòu)成了完整的 react 執(zhí)行圖了:

到此這篇關(guān)于React commit源碼分析詳解的文章就介紹到這了,更多相關(guān)React commit內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解
這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
Shopee在React?Native?架構(gòu)方面的探索及發(fā)展歷程
這篇文章主要介紹了Shopee在React?Native?架構(gòu)方面的探索,本文會從發(fā)展歷史、架構(gòu)模型、系統(tǒng)設(shè)計、遷移方案四個方向逐一介紹我們?nèi)绾我徊讲降貪M足多團隊在復(fù)雜業(yè)務(wù)中的開發(fā)需求,需要的朋友可以參考下2022-07-07
React實現(xiàn)一個倒計時hook組件實戰(zhàn)示例
這篇文章主要為大家介紹了React實現(xiàn)一個倒計時hook組件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02
React+TypeScript項目中使用CodeMirror的步驟
CodeMirror被廣泛應(yīng)用于許多Web應(yīng)用程序和開發(fā)工具,之前做需求用到過codeMirror這個工具,覺得還不錯,功能很強大,所以記錄一下改工具的基礎(chǔ)用法,對React+TypeScript項目中使用CodeMirror的步驟感興趣的朋友跟隨小編一起看看吧2023-07-07
React Native中ScrollView組件輪播圖與ListView渲染列表組件用法實例分析
這篇文章主要介紹了React Native中ScrollView組件輪播圖與ListView渲染列表組件用法,結(jié)合實例形式詳細分析了ScrollView組件輪播圖與ListView渲染列表組件具體功能、使用方法與操作注意事項,需要的朋友可以參考下2020-01-01
ReactHook使用useState更新變量后,如何拿到變量更新后的值
這篇文章主要介紹了ReactHook使用useState更新變量后,如何拿到變量更新后的值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

