主要分析[[react]]类组件中useEffect``useLayoutEffect的实现逻辑
useEffect
render阶段
和其他hook函数一样,useEffect在挂载节点和更新阶段会调用不同的实现函数。
mountEffect
function mountEffect(
create: () => (() => void) | void,
deps: Array[] | void
) {
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps
)
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
// 新建一个hook对象
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 当前fiber需要设置flags,作用是什么?
currentlyRenderingFiber.flags |= fiberFlags;
// 更新hook的memoizedState属性,这个属性的作用是什么?
hook.memoizedState = pushEffect(
// 这里的值就是 HookHasEffect | PassiveEffect
HookHasEffect | hookFlags,
create,
undefined,
nextDeps
)
}
// 目标就是将effect对象挂在到当前fiber的更新队列上
function pushEffect(tag, create, destory, deps) {
// 回调函数,依赖都包装成effect对象
const effect = {
tag,
create,
destory,
deps,
next
}
// 获取当前fiber的updateQueue
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (componentUpdateQueue === null) {
// 没有更新队列,effect就是唯一的新元素
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 存在队列
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect == null) {
// 更新队列空,effect就是唯一的新元素
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 就是把新建的effect连成最后一个,新建的effect.next继续指向第一个
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
stores: null
}
}
// 当只调用一次useEffect时,updateQueue是这样的:
[updateQueue]--lastEffect--+
|
V
+---->[effect]--next--+
| |
+---------------------+
// 当调用2次useEffect时,最终updateQueue是这样的:
[updateQueue]--lastEffect------------------+
|
V
+---->[effect]--next------>[effect]--next--+
| |
+------------------------------------------+
小结
- 每个
mountEffect都会创建一个effect对象 - 创建的effect保存到当前hook对象的memoizedState
- 创建的effect对象函数组件fiber的updateQueue的lastEffect永远指向最后一个effect对象
updateEffect
function updateEffect(create, deps) {
// 比创建的时候少了PassiveStaticEffect
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
// 获取当前fiber对应的hook对象
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destory = null;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destory = prevEffect.destory;
// 判断是否设置依赖项,即effect的第二个参数
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// 新旧依赖会做一次浅比较
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 新旧依赖是一致的,不需要执行create
hook.memoizedState = pushEffect(
hookFlags, // hookFlags是 PassiveEffect
create,
destory,
nextDeps
);
return;
}
}
}
// 没有旧依赖,或者新旧依赖是不一致的,需要执行create
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags, // hookFlags是 HookHasEffet | PassiveEffect
create,
destory,
nextDeps
)
}
小结
- 每个
updateEffect的同样会创建一个effect对象 - 创建effect对象同样会关联到当前hook对象
- 创建的effect对应会串联到当前fiber对象的updateQueue中
- 注意是否按effect是否需要在更新的时候执行分别设置了不同的hookEffet
commit阶段
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffet : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
// 只有effect满足特定的flags,才会执行create函数
if ((effect.flags & flags) === flags) {
// 执行回调函数
const create = effect.create;
// 保存清理函数
effect.destory = create();
}
// 遍历所有的effect对象
effect = effect.next;
} while(effect !== firstEffect)
}
}
// 那么可以看下调用时时传递的flags:
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,
finishedWork: Fiber,
) {
switch(finishedWork.tag) {
case FunctionConponent:
// 入参是HookPassive | HookHasEffect
// 初次渲染、更新渲染需要执行effect、更新渲染不需要执行effect时hook.flags
// 初次渲染: HookHasEffect | PassiveEffect
// 更新需要执行effect: HookHasEffect | PassiveEffect
// 更新不需要执行effect: PassiveEffect
commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork)
}
}
小结
- 不管是初次渲染还是更新渲染,
useEffect都会创建对应的effect对象,并串联到fiber的updataQueue上 - effect对象具备flag属性,在render阶段设置不同的值,随后在commit只有对flag是
HookPassive | HookHasEffect的effect才会执行
useLayoutEffect
function mountLayouteEffect(
create: () => (() => void) | void,
deps: Array<mixed>
) {
let fiberFlags = UpdateEffect;
// 和mountEffect实现一样
return mountEffectImpl(fiberFlags, HookLayout, create, deps);
}
function updateLayouEffect(
create: () => (() => void) | void,
deps: Array<mixed>
) {
// 同updateEffect实现一样
return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}
发现在render阶段,useLayoutEffect和useEffect的实现是一样的,区别在于第二个参数,useEffect使用HookPassive,useLayoutEffect使用HookLayout
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber,
workInProgress: Fiber
) {
switch(finishedWork.tag) {
case FunctionComponent: {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
}
}
在commit阶段也是一样的实现,将入参设置为HookLayout | HookHasEffect即可以了。
小结
useLayoutEffect的实现和useEffect可以说是一样的:render阶段创建effect对象,commit阶段遍effect列表,仅执行具有特定flag的effect对象。
useEffect是异步的
上面总结的useEffect和useLayoutEffect的共同点,但2个接口有一个明显的区别:useEffect是异步的,useLayoutEffect是同步的。
function commitRootImpl(root: FiberRoot) {
const finishedWork = root.finishedWork;
// 如果当前fiber节点或子树置PassiveMask,说明存在useEffect
if (finishedWork.subtreeFlags & PassiveMask !== NoFlags ||
finishedWork.flags PassiveMask !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
// 让scheduler模块在适当的时候执行回调
sheduleCallback(NormalSchedulerPriority, () => {
// 最终会调用commitPassiveMountOnFiber,执行所有的useEffect回调
flushPassiveEffects();
return null;
})
}
}
}
在commit阶段,在真正的执行commit之前会判断是否需要执行useEffect,所以有的话则在回调函数中统一执行,所以useEffect是异步的。