前言
在上篇我們介紹了 commitRoot的整體流程,那麼本篇就來介紹它的第一個子階段「before mutation
」:
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
//刪除了 dev 程式碼
} else {
try {
//呼叫 classComponent 上的生命週期方法 getSnapshotBeforeUpdate
//關於getSnapshotBeforeUpdate,請看:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
commitBeforeMutationEffects();
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
複製程式碼
一、commitBeforeMutationEffects()
作用:
迴圈effect
鏈,對有Snapshot
的effect
執行commitBeforeMutationEffectOnFiber
原始碼:
function commitBeforeMutationEffects() {
//迴圈 effect 鏈
while (nextEffect !== null) {
//如果 effectTag 裡有 Snapshot 這個 effectTag 的話
//關於&,請看[前端小知識10點(2020.2.10)](https://mp.weixin.qq.com/s/tt2XcW4GF7oBBZOPwTiCcg)中的「8、JS 中的 & 是什麼意思」
if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
//dev 可不看
// setCurrentDebugFiberInDEV(nextEffect);
//計 effect 的數
recordEffect();
//獲取當前 fiber 節點
const current = nextEffect.alternate;
commitBeforeMutationEffectOnFiber(current, nextEffect);
//dev 可不看
// resetCurrentDebugFiberInDEV();
}
nextEffect = nextEffect.nextEffect;
}
}
複製程式碼
解析:
(1) nextEffect
是effect
鏈上從firstEffect
到lastEffect
的每一個需要commit
的fiber
物件
(2) 當nextEffect
上有Snapshot
這個effectTag
時,執行commitBeforeMutationEffectOnFiber()
,讓不同型別的元件執行不同的操作,來提交(commit
)相關effect
二、commitBeforeMutationEffectOnFiber()
作用:
(1) 如果該fiber
型別是ClassComponent
的話,執行getSnapshotBeforeUpdate
生命週期api
,將返回的值賦到fiber
物件的__reactInternalSnapshotBeforeUpdate
上
(2) 如果該fiber
型別是FunctionComponent
的話,執行hooks
上的effect
相關 API
(3) 關於&
運算子,請看:
前端小知識10點(2020.2.10) 中的「8、JS 中的 & 是什麼意思
」
原始碼:
function commitBeforeMutationLifeCycles(
current: Fiber | null,
finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
//FunctionComponent會執行commitHookEffectList()
//FunctionComponent是 pureComponent,所以不會有副作用
//useEffect 和 useLayoutEffect 是賦予FunctionComponent有副作用能力的 hooks
//useEffect類似於componentDidMount,useLayoutEffect類似於componentDidUpdate
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
//提交 hooks 的 effects
commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
return;
}
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
if (current !== null) {
//老 props
const prevProps = current.memoizedProps;
//老 state
const prevState = current.memoizedState;
//getSnapshotBeforeUpdate 的計時開始
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
//獲取 classComponent 的例項
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
//刪除了 dev 程式碼
}
//執行 getSnapshotBeforeUpdate 生命週期 api,在元件update前捕獲一些 DOM 資訊,
//返回自定義的值或 null,統稱為 snapshot
//關於getSnapshotBeforeUpdate,請參考:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
if (__DEV__) {
//刪除了 dev 程式碼
}
//將 snapshot 賦值到__reactInternalSnapshotBeforeUpdate屬性上,
// 這種手法跟[React原始碼解析之updateClassComponent(上)](https://mp.weixin.qq.com/s/F_UdPgdt6wtP78eDqUesoA)
// 中的「三、adoptClassInstance」裡 instance._reactInternalFiber=workInProgress 類似
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
//getSnapshotBeforeUpdate 的計時結束
stopPhaseTimer();
}
}
return;
}
case HostRoot:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
//沒有副作用,不應該進入到 commit 階段
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
複製程式碼
解析:
(1) 根據fiber
的tag
即型別,進行不同的操作。主要看FunctionComponent
和ClassComponent
這兩種情況。
(2) 如果是FunctionComponent
的話,則執行commitHookEffectList()
,該方法稍後解析。
(3) 如果是ClassComponent
的話,則執行生命週期方法——getSnapshotBeforeUpdate()
,並將返回值賦予給fiber
物件上的__reactInternalSnapshotBeforeUpdate
屬性上。
也就是說,類元件的子階段「before mutation
」 目的是:
呼叫getSnapshotBeforeUpdate()
,在commit
前獲取 DOM 相關資訊
(4) getSnapshotBeforeUpdate()
在最近一次渲染輸出(提交到 DOM 節點)之前呼叫。
它使得元件能在發生更改之前從 DOM 中捕獲一些資訊(例如,滾動位置)。
此生命週期的任何返回值將作為引數傳遞給componentDidUpdate()
,更多詳情請參考:
zh-hans.reactjs.org/docs/react-…
(5)
instance.__reactInternalSnapshotBeforeUpdate = snapshot
複製程式碼
這種手法跟 React原始碼解析之updateClassComponent(上) 中的「三、adoptClassInstance
」裡的
instance._reactInternalFiber=workInProgress
複製程式碼
類似
三、commitHookEffectList()
說明:useEffect
是讓FunctionComponent
產生副作用的hooks
,當使用useEffect
後,會在fiber
上的updateQueue.lastEffect
生成effect
鏈,具體請看ReactFiberHooks.js中的pushEffect()
作用:
迴圈FunctionComponent
上的effect
鏈,並根據每個effect
上的effectTag
,執行destroy/create
操作(作用類似於componentDidMount
/componentWillUnmount
)
原始碼:
function commitHookEffectList(
unmountTag: number,
mountTag: number,
finishedWork: Fiber,
) {
//FunctionComponent 的更新佇列
//補充:FunctionComponent的 side-effect 是放在 updateQueue.lastEffect 上的
//ReactFiberHooks.js中的pushEffect()裡有說明: componentUpdateQueue.lastEffect = effect.next = effect;
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
//如果有副作用 side-effect的話,迴圈effect 鏈,根據 effectTag,執行每個 effect
if (lastEffect !== null) {
//第一個副作用
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
//如果包含 unmountTag 這個 effectTag的話,執行destroy(),並將effect.destroy置為 undefined
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
//如果包含 mountTag 這個 effectTag 的話,執行 create()
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
const create = effect.create;
effect.destroy = create();
if (__DEV__) {
//刪除了 dev 程式碼
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
複製程式碼
解析:
(1) 不同於ClassComponent
的 effect 鏈直接取fiber.firstEffect
—>fiber.firstEffect.next
……,
FunctionComponent
的 effect 鏈取的是fiber.updateQueue.lastEffect
—>fiber.updateQueue.lastEffect. next
….
(2) 迴圈FunctionComponent
上的 effect 鏈,
如果effectTag
是unmountTag
的話,就執行effect.destroy()
方法,感覺跟componentWillUnmount()
作用類似;
如果effectTag
是mountTag
的話,就執行effect.create()
方法,感覺跟componentDidMount()
作用類似;
總結
在「before mutation
」子階段上,ClassComponent
會執行getSnapshotBeforeUpdate()
,捕獲 DOM 資訊;FunctionComponent
會執行effect.create()
/effect.destory()
,類似於componentDidMount()
/componentWillUnmount()
流程圖
GitHubcommitBeforeMutationEffects()
:
github.com/AttackXiaoJ…
commitBeforeMutationLifeCycles()
/commitHookEffectList()
:
github.com/AttackXiaoJ…
(完)