代码都来自 React 18 源码, 大家可以放心食用
useEffect 原理
问题: 下面这段代码,从挂载到更新发生了什么?怎么挂载的?怎么更新的。
function Counter() {
useEffect(() => {
console.log("useEffect1");
return () => {
console.log("destroy useEffect1");
};
});
useEffect(() => {
console.log("useEffect2");
return () => {
console.log("destroy useEffect2");
};
});
useEffect(() => {
console.log("useEffect3");
return () => {
console.log("destroy useEffect3");
};
});
return <div></div>;
}
这里,我们直接进入到 reconciler 阶段,默认已经通过深度优先调度到了 Counter 函数组件的 Fiber节点
useEffect mount 挂载阶段
第一:判断是函数节点的 tag 之后,调用 renderWithHooks.
/*
workInProgress: 当前工作的 Fiber 节点
Componet:Counter 函数组件
_current: 老 Fiber 节点 也就是 workInProgress.alternate
*/
let value = renderWithHooks(_current,workInProgress,Component);
第二:在 renderWithHooks 当中调用 Counter 函数
let children = Component();
第三: 调用 Counter 函数 的 useEffect 函数
export function useEffect(create, deps) {
return ReactCurrentDispatcher.current.useEffect(create, deps);
}
第四:挂载阶段 ReactCurrentDispatcher.current.useEffect 实则是调用了 mountEffect, moutEffect 调用了 mountEffectImpl
return mountEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps
);
第五:在 mountEffectImpl 中创建 调用 mountWorkInProgressHook 和 pushEffect 方法
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps
);
}
mountWorkInProgressHook 函数,创建 useEffect 的 Hook 对象,构建 fiber.memoizedState 也就是 Hook 链表
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
pushEffect 函数, 创建 effect 对象 ,赋值给 hook.memoizedState,构建 fiber.updateQueue队列,队列当中就是一个一个的 effect 对象。
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
// Circular
next: (null: any),
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
前五步已经初始化 mount 完毕,第二个 和 第三个 都是按照此流程,得到了以下的结果。
第六:等到 renderer 阶段中的 beforeMutaion 阶段,将 flushPassiveEffectsImpl 异步调度
function performSyncWorkOnRoot(root) {
flushPassiveEffects();
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
return null;
}
flushPassiveEffectsImpl函数:
function flushPassiveEffectsImpl() {
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current, lanes, transitions);
return true;
}
第七:在 renderer 阶段完成之后(Dom 已经完成挂载),flushPassiveEffectsImpl函数 中 调用 commitPassiveUnmountEffects commitPassiveMountEffects,经过一系列的函数调用到达 commitHookEffectListMount, commitHookEffectListUnmount 执行传入 useEffect 的 mount 函数和 返回的 unMount 函数。(挂载时 unMount 函数为 null)
commitHookEffectListMount函数: 遍历 fiber.updateQueue 以此执行传入 useEffect 的 create 函数
function commitHookEffectListMount(flags: HookFlags, 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 {
effect.destroy = create();
effect = effect.next;
} while (effect !== firstEffect);
}
}
commitHookEffectListUnmount 函数,执行 返回的 unMount 函数
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
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 {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
// 在此函数中 调用 distroy
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
effect = effect.next;
} while (effect !== firstEffect);
}
}
自此 useEffect 挂载阶段执行完毕
useEffect update 更新阶段
第一:判断是函数节点的 tag 之后,调用 renderWithHooks.
/*
workInProgress: 当前工作的 Fiber 节点
Componet:Counter 函数组件
_current: 老 Fiber 节点 也就是 workInProgress.alternate
*/
let value = renderWithHooks(_current,workInProgress,Component);
第二:在 renderWithHooks 当中调用 Counter 函数
let children = Component();
第三: 调用 Counter 函数的第一个 useEffect 函数
export function useEffect(create, deps) {
return ReactCurrentDispatcher.current.useEffect(create, deps);
}
第四:更新阶段 ReactCurrentDispatcher.current.useEffect 实则是调用了 updateEffect, moutEffect 调用了 updateEffectImpl
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps
);
第五:updateEffectImpl updateWorkInProgressHook 复用 currentHook 的属性 创建新的 Hook 对象 以及 新的 Hook 链表, 调用 areHookInputsEqual 函数判断依赖是否发生变化,没有变化直接 return ,有变化调用 pushEffects
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps,
);
}
pushEffect 函数 ,创建 effect 对象 ,赋值给 hook.memoizedState,构建 fiber.updateQueue队列,队列当中就是一个一个的 effect 对象。
第六:等到 renderer 阶段中的 beforeMutaion 阶段(commitRoot 之前),将 flushPassiveEffectsImpl 异步调度
function performSyncWorkOnRoot(root) {
flushPassiveEffects();
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
return null;
}
flushPassiveEffectsImpl函数:
function flushPassiveEffectsImpl() {
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root, root.current, lanes, transitions);
return true;
}
第七:在 renderer 阶段完成之后(layout 阶段结束之后),flushPassiveEffectsImpl函数 中 调用 commitPassiveUnmountEffects commitPassiveMountEffects,经过一系列的函数调用到达 commitHookEffectListMount, commitHookEffectListUnmount 执行传入 useEffect 的 mount 函数和 返回的 unMount 函数。(更新的时候执行的 destroy 函数是上一次的 useEffect 的返回的函数)
// 更新时候 调用 pushEffects destroy 已经有值,是上一次的 useEffect 的返回的函数
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
commitHookEffectListMount函数: 遍历 fiber.updateQueue 以此执行传入 useEffect 的 create 函数
function commitHookEffectListMount(flags: HookFlags, 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 {
effect.destroy = create();
effect = effect.next;
} while (effect !== firstEffect);
}
}
commitHookEffectListUnmount 函数,执行 返回的 unMount 函数
function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null,
) {
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 {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
// 在此函数中 调用 distroy
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
effect = effect.next;
} while (effect !== firstEffect);
}
}
自此 useEffect 更新完毕
useLayoutEffect 原理
useLayoutEffect mount 挂载阶段
第一:判断是函数节点的 tag 之后,调用 renderWithHooks.
/*
workInProgress: 当前工作的 Fiber 节点
Componet:Counter 函数组件
_current: 老 Fiber 节点 也就是 workInProgress.alternate
*/
let value = renderWithHooks(_current,workInProgress,Component);
第二:在 renderWithHooks 当中调用 Counter 函数
let children = Component();
第三: 调用 Counter 函数的第一个 useLayoutEffect 函数
export function useLayoutEffect(create, deps) {
return ReactCurrentDispatcher.current.useLayoutEffect(create, deps);
}
第四:挂载阶段 ReactCurrentDispatcher.current.useEffect 实则是调用了 mountLayoutEffect, mountLayoutEffect 调用了 mountLayoutEffect
第五:在 mountEffectImpl 中创建 调用 mountWorkInProgressHook 和 pushEffect 方法
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
mountWorkInProgressHook 函数,创建 useLayoutEffect 的 Hook 对象,构建 fiber.memoizedState 也就是 Hook 链表
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
pushEffect 函数, 创建 effect 对象 ,赋值给 hook.memoizedState,构建 fiber.updateQueue队列,队列当中就是一个一个的 effect 对象。
function pushEffect(tag, create, destroy, deps) {
const effect: Effect = {
tag,
create,
destroy,
deps,
// Circular
next: (null: any),
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
第六:等到 renderer 阶段中的 Mutation(Dom 挂载中阶段) 阶段,同步调用 layoutEffect 的 destroy 函数。
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
// Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
// 同步调用 layoutEffect 的 destroy 函数
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
// 同步调用 layoutEffect 的 destroy 函数
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
}
第七:等到 renderer 阶段中的 Layout (Dom 挂载后) 阶段,同步调用 useLayoutEffect 的 create 函数。
function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
// 同步调用 useLayoutEffect 的 create 函数。
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
// 同步调用 useLayoutEffect 的 create 函数。
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
自此 useLayoutEffect 挂载阶段执行完毕
useLayoutEffect update 更新阶段
第一:判断是函数节点的 tag 之后,调用 renderWithHooks.
/*
workInProgress: 当前工作的 Fiber 节点
Componet:Counter 函数组件
_current: 老 Fiber 节点 也就是 workInProgress.alternate
*/
let value = renderWithHooks(_current,workInProgress,Component);
第二:在 renderWithHooks 当中调用 Counter 函数
let children = Component();
第三: 调用 Counter 函数的第一个 useEffect 函数
export function useEffect(create, deps) {
return ReactCurrentDispatcher.current.useEffect(create, deps);
}
第四:更新阶段 ReactCurrentDispatcher.current.useEffect 实则是调用了 updateEffect, moutEffect 调用了 updateEffectImpl
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps
);
第五:updateEffectImpl 函数: updateWorkInProgressHook 函数 复用 currentHook 的属性 创建新的 Hook 对象 以及 新的 Hook 链表, 调用 areHookInputsEqual 函数判断依赖是否发生变化,没有变化直接 return ,有变化调用 pushEffects
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps,
);
}
第六:等到 renderer 阶段中的 Mutation(Dom 挂载中阶段) 阶段,同步调用 layoutEffect 的 destroy 函数。
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
// Layout effects are destroyed during the mutation phase so that all
// destroy functions for all fibers are called before any create functions.
// This prevents sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
// 同步调用 layoutEffect 的 destroy 函数
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
// 同步调用 layoutEffect 的 destroy 函数
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
return;
}
}
第七:等到 renderer 阶段中的 Layout (Dom 挂载后) 阶段,同步调用 useLayoutEffect 的 create 函数。
function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (shouldProfile(finishedWork)) {
try {
startLayoutEffectTimer();
// 同步调用 useLayoutEffect 的 create 函数。
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
recordLayoutEffectDuration(finishedWork);
} else {
try {
// 同步调用 useLayoutEffect 的 create 函数。
commitHookEffectListMount(hookFlags, finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
自此 useLayoutEffect 更新阶段执行完毕