以下源码浅析的React版本为17.0.1,使用ReactDOM.render创建的同步应用,不含优先级相关。
数据结构
Effect
类型保存相关信息
type Effect = {|
tag: HookFlags, // 标识是useEffect还是useLayoutEffect(HasEffect、Layout、Passive )
create: () => (() => void) | void, // 回调函数
destroy: (() => void) | void, // 销毁回调函数
deps: Array<mixed> | null, // 依赖数组
next: Effect, // 下一个Effect
|};
函数组件的Effect
信息保存在函数组件Fiber节点的updateQueue
字段,updateQueue
为一个单向环形链表。
type FunctionComponentUpdateQueue = {|lastEffect: Effect | null|};
Fiber.updateQueue.lastEffect
为最后一个Effect
,lastEffect.next
为第一个Effect
。
这里值得注意的是Effect
对象除了赋值给了updateQueue
,同时也会赋值给Fiber节点中Hooks
链表的对应Hook
的memoizedState
属性,用于后续的对比。
useEffect和useLayoutEffect
之前的文章里讲到了Mount时和Update时用的Hook
不是同一个,但是useEffect
和useLayoutEffect
在Mount和Update时用的都是同一个方法,只是传入了不同的参数。
mountEffect和mountLayoutEffect
// Mount时useEffect
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
UpdateEffect | PassiveEffect, // 赋值给Fiber.flags,与useLayoutEffect时不同的是多了个PassiveEffect
HookPassive, // 赋值给effect.tag
create, // 回调函数
deps, // 依赖数组
);
}
// Mount时的useLayoutEffect
function mountLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return mountEffectImpl(
UpdateEffect,
HookLayout, // 与useEffect不同
create,
deps,
);
}
mountEffect
和mountLayoutEffect
内部都是调用了mountEffectImpl
,区别只是flags
的标识不同,用于区分是useEffect
还是useLayoutEffect
。
updateEffect和updateLayoutEffect
// Update时useEffect
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(
UpdateEffect | PassiveEffect,
HookPassive,
create,
deps,
);
}
// Update时useLayoutEffect
function updateLayoutEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
和Mount时一样,Update时useEffect
和useLayoutEffect
内部使用的相同函数。
mountEffectImpl
mountEffectImpl
函数里主要给Fiber节点添加了对应的flags
,同时处理函数组件里Hooks
链表,将Effect
对象赋值给对应的workInProgressHook
的memoizedState
。
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
// 处理hooks链表同时返回workInProgressHook
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 为当前的Fiber节点添加flags
currentlyRenderingFiber.flags |= fiberFlags;
// pushEffect会返回Effect,同时赋值给workInProgressHook的memoizedState属性
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}
内部调用了pushEffect
函数来创建Effect
对象。
pushEffect
pushEffect
函数创建了Effect
对象,并组装updateQueue
的单向环形链表。
function pushEffect(tag, create, destroy, deps) {
// effect对象
const effect: Effect = {
tag, // effect的tag,用于区分useEffect和useLayoutEffect
create, // 回调函数
destroy, // 销毁函数
deps, // 依赖数组
// 环形链表
next: (null: any),
};
let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
// fiber节点不存在updateQueue则需要初始化
if (componentUpdateQueue === null) {
// 创建新的updateQueue
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
// 初始值
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 单向环形链表 lastEffect为最新的effect,lastEffect.next为第一个effect
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
当updateQueue
不存在时,调用createFunctionComponentUpdateQueue
创建新的updateQueue
,否则就将新的effect
添加到链表里。
createFunctionComponentUpdateQueue
createFunctionComponentUpdateQueue
方法创建一个新的updateQueue
。
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
return {
lastEffect: null,
};
}
updateEffectImpl
updateEffectImpl
函数内部多了一个判断传入的依赖数组是否相等的判断。
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
// 获取workInProgressHook,改变currentHook和workInProgressHook的指向
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
// 上一次的effect对象
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
// 上一次的依赖数组
const prevDeps = prevEffect.deps;
// 判读两个依赖数组是否相同
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(
hookFlags, // 对应的effect tag少了HookHasEffect,可视作无变化
create,
destroy, // 和mount时不同传入了destroy
nextDeps,
);
// 直接return
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps,
);
}
到现在分析了useEffect
和useLayoutEffect
在Mount和Update时如何创建Effect
对象,与useState
不同的时,它们不能通过调用dispatchAction
来主动触发更新,而是随着useState
变化触发更新的同时随着Fiber树的构建在commit
阶段执行回调函数和销毁函数。
但是useEffect
和useLayoutEffect
的回调函数和销毁函数执行的时机是不同的,这也是它们之间的直接区别。
useEffect的异步执行
当workInProgress
Fiber树构建完成,进入commit
阶段后,会异步调用useEffect
的回调函数和销毁函数。
commit
阶段内部又分为3个阶段
- Before Mutation阶段
- Mutation阶段
- Layout阶段
发起useEffect
调度是在Before Mutation阶段执行的。
useEffect
是异步调度的,需要执行回调函数和销毁函数的useEffect
是在Layout阶段执行收集的,所以在最终异步处理useEffect
的时候已经收集好了。
发起useEffect调度
Before Mutation阶段会执行commitBeforeMutationEffects
函数,这个函数同时也会执行类组件的getSnapshotBeforeUpdate
生命周期。
function commitBeforeMutationEffects() {
// 省略无关代码...
while (nextEffect !== null) {
const flags = nextEffect.flags;
// 当flags包含Passive时表示有调用useEffect
if ((flags & Passive) !== NoFlags) {
if (!rootDoesHavePassiveEffects) {
// 将全局标识赋值为true,一个异步调度就会处理所有的useEffect,避免发起多个
rootDoesHavePassiveEffects = true;
// 通过调度器发起一个异步调度
scheduleCallback(NormalSchedulerPriority, () => {
// 处理useEffect
flushPassiveEffects();
return null;
});
}
}
// 遍历有副作用的Fiber节点
nextEffect = nextEffect.nextEffect;
}
}
flushPassiveEffects
函数内部会调用flushPassiveEffectsImpl
函数,在这里会执行回调函数和销毁函数,因为是异步调度的,已经是渲染结束后了。
收集需要处理的useEffect
上面说到需要执行回调函数和销毁函数的useEffect
是在Layout阶段执行收集的。
Layout阶段会执行commitLayoutEffects
函数,其中flags
包含Update
的Fiber节点的会执行commitLifeCycles
函数。
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
// 这里执行了useLayoutEffect的回调函数
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
// 收集需要处理的useEffect
schedulePassiveEffects(finishedWork);
return;
}
// 省略无关代码...
}
FunctionComponent
会执行schedulePassiveEffects
函数,schedulePassiveEffects
函数中收集了需要执行回调函数和销毁函数的useEffect
。
function schedulePassiveEffects(finishedWork: Fiber) {
// updateQueue环形链表同时存了useEffect和useLayoutEffect的Effect对象
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
// 遍历updateQueue
do {
const {next, tag} = effect;
if (
// HookPassive标识useEffect
(tag & HookPassive) !== NoHookEffect &&
// 当依赖数组没有发生变化时pushEffect的调用没有传入HookHasEffect,所以会被排除
(tag & HookHasEffect) !== NoHookEffect
) {
// 需要执行销毁函数
enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
// 需要执行回调函数
enqueuePendingPassiveHookEffectMount(finishedWork, effect);
}
effect = next;
} while (effect !== firstEffect);
}
}
需要执行销毁函数和回调函数的Effect
对象分别存在两个数组中,数组的偶数下标为Effect
对象,奇数下标为Fiber节点。
// 存需要执行回调函数
let pendingPassiveHookEffectsMount: Array<HookEffect | Fiber> = [];
// 存需要执行销毁函数
let pendingPassiveHookEffectsUnmount: Array<HookEffect | Fiber> = [];
再看如何把Effect
对象存入数组的。
// 需要执行回调函数
export function enqueuePendingPassiveHookEffectMount(
fiber: Fiber,
effect: HookEffect,
): void {
// 一次push两个不同数据,一个Effect对象,一个Fiber节点
pendingPassiveHookEffectsMount.push(effect, fiber);
// 省略代码...
}
// 需要执行销毁函数
export function enqueuePendingPassiveHookEffectUnmount(
fiber: Fiber,
effect: HookEffect,
): void {
pendingPassiveHookEffectsUnmount.push(effect, fiber);
// 省略代码...
}
执行回调函数和销毁函数
这个过程由调度器异步调度执行,执行的函数为flushPassiveEffects
的内部函数flushPassiveEffectsImpl
。
function flushPassiveEffectsImpl() {
// 省略代码...
const unmountEffects = pendingPassiveHookEffectsUnmount;
pendingPassiveHookEffectsUnmount = [];
// 执行销毁函数destroy函数
// 偶数下标为HookEffect,奇数下标为fiber节点
for (let i = 0; i < unmountEffects.length; i += 2) {
const effect = ((unmountEffects[i]: any): HookEffect);
const fiber = ((unmountEffects[i + 1]: any): Fiber);
const destroy = effect.destroy;
effect.destroy = undefined;
if (typeof destroy === 'function') {
try {
destroy();
} catch (error) {
captureCommitPhaseError(fiber, error);
}
}
}
// 执行回调函数create函数
const mountEffects = pendingPassiveHookEffectsMount;
pendingPassiveHookEffectsMount = [];
for (let i = 0; i < mountEffects.length; i += 2) {
const effect = ((mountEffects[i]: any): HookEffect);
const fiber = ((mountEffects[i + 1]: any): Fiber);
try {
const create = effect.create;
effect.destroy = create();
} catch (error) {
invariant(fiber !== null, 'Should be working on an effect.');
captureCommitPhaseError(fiber, error);
}
}
// 省略代码...
}
useLayoutEffect的同步执行
和useEffect
不同,useLayoutEffect
就完全是同步的了,并且不需要像useEffect
一样去收集Effect
对象,而是直接通过updateQueue
执行。
useLayoutEffect
的回调函数执行在Layout阶段,销毁函数执行在Mutation阶段。
执行回调函数
上面说到Layout阶段会执行commitLifeCycles
函数。
function commitLifeCycles(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
// 执行useLayoutEffect的回调函数
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
// 收集需要处理的useEffect
schedulePassiveEffects(finishedWork);
return;
}
// 省略无关代码...
}
在commitLifeCycles
函数里调用了commitHookEffectListMount
函数执行useLayoutEffect
的回调。
commitHookEffectListMount
function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
// 取出Fiber节点的updateQueue
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
// 遍历执行回调函数create函数
do {
// (tag => HookLayout | HookHasEffect) 标识effect对象为useLayoutEffect
if ((effect.tag & tag) === tag) {
// 执行回调函数
const create = effect.create;
effect.destroy = create();
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
执行销毁函数
销毁函数的执行在Mutation阶段,Mutation阶段会执行commitMutationEffects
函数,函数内部会对flags
包含Update
的Fiber节点再执行commitWork
函数。
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
case Block: {
// 执行useLayoutEffect的销毁函数
commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
return;
}
}
}
commitHookEffectListUnmount
commitHookEffectListUnmount
函数和commitHookEffectListMount
函数逻辑那还就是一样。
function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
// 取出Fiber节点的updateQueue
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 & tag) === tag) {
// 执行销毁函数
const destroy = effect.destroy;
effect.destroy = undefined;
if (destroy !== undefined) {
destroy();
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
至此useLayoutEffect
执行完毕。
useImperativeHandle
useImperativeHandle
相当于是一个useLayoutEffect
的语法糖。
Mount
function mountImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
return mountEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
Update
function updateImperativeHandle<T>(
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
create: () => T,
deps: Array<mixed> | void | null,
): void {
const effectDeps =
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
return updateEffectImpl(
UpdateEffect,
HookLayout,
imperativeHandleEffect.bind(null, create, ref),
effectDeps,
);
}
内部使用的依然是mountEffectImpl
方法和updateEffectImpl
方法,唯一不同的是create
函数传入的是经过处理的imperativeHandleEffect
。
imperativeHandleEffect
imperativeHandleEffect
方法即是一个create
方法,同时返回destroy
函数。
function imperativeHandleEffect<T>(
create: () => T,
ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,
) {
if (typeof ref === 'function') {
const refCallback = ref;
const inst = create();
refCallback(inst);
return () => {
refCallback(null);
};
} else if (ref !== null && ref !== undefined) {
const refObject = ref;
const inst = create();
refObject.current = inst;
return () => {
refObject.current = null;
};
}
}
函数内部会通过判断ref
为对象还是回调函数,分别执行不同的逻辑,返回不同的destroy
函数。知道了内部的原理,其实可以很容易的用useLayoutEffect
复现。
function useFakeImperativeHandle(ref, create, deps) {
useLayoutEffect(() => {
const inst = create();
if (typeof ref === "function") {
ref(inst);
return () => {
ref(null);
};
} else if (ref) {
ref.current = inst;
return () => {
ref.current = null;
};
}
}, deps);
}
总结
useEffect
和useLayoutEffect
的函数本身在Mount和Update时调用的都是相同的函数,仅参数不同,最大的区别在于useEffect
是异步执行,useLayoutEffect
是同步执行。
useEffect
和useLayoutEffect
所使用的Effect
对象储存在函数组件的Fiber节点的updateQueue
中,它是一个单向环形链表,updateQueue.lastEffect
为最新的Effect
对象,lastEffect.next
为第一个Effect
对象,同时为了维护函数组件的Hooks
链表,Effect
对象也同时被添加到了Fiber节点的memorizedState
属性中。
Effect
对象通过tag
字段区分是useEffect
还是useLayoutEffect
,HookPassive
为useEffect
,HookLayout
为useLayoutEffect
,HookHasEffect
标记Effect
的回调和销毁函数需要执行。
在Fiber树的render阶段通过renderWithHooKS
方法执行函数组件,同时会执行内部的Hook
,函数组件执行完成后创建了储存Effect
对象的updateQueue
链表。
在commit阶段,useEffect
会在Before Mutation阶段通过commitBeforeMutationEffects
函数发起异步调度,在Layout阶段通过函数commitLayoutEffects
将需要执行回调函数和销毁函数的Effect
分别收集到pendingPassiveHookEffectsMount
和pendingPassiveHookEffectsUnmount
数组。在commit阶段完毕后会经过调度器执行回调函数和销毁函数。
useLayoutEffect
是同步执行的,它的销毁函数在Mutation阶段通过commitMutationEffects
函数最终调用commitHookEffectListUnmount
函数执行。它的回调函数会在 Layout阶段通过commitLayoutEffects
函数最终调用commitHookEffectListMount
函数执行。
如有错误,还望交流指正。