Hooks 源码:useEffect, useLayoutEffect
定义
你可以在官方文档中看到 useEffect
的说明。
源码
生成 effect
function mountEffect(create: () => (() => void) | void,deps: Array<mixed> | void | null): void {return mountEffectImpl(PassiveEffect | PassiveStaticEffect,HookPassive,create,deps)}function mountLayoutEffect(create: () => (() => void) | void,deps: Array<mixed> | void | null): void {return mountEffectImpl(UpdateEffect, HookLayout, create, deps)}function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {const hook = mountWorkInProgressHook()const nextDeps = deps === undefined ? null : depscurrentlyRenderingFiber.flags |= fiberFlagshook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,undefined,nextDeps)}
在 mount
阶段,通过 pushEffect
添加该 effect
到 updateQueue
中。
注意 useEffect
被标记了 HookPassive
和 HookHasEffect
。而对于 useLayoutEffect
则标记了 HookLayout
。
function updateEffect(create: () => (() => void) | void,deps: Array<mixed> | void | null): void {return updateEffectImpl(PassiveEffect, HookPassive, create, deps)}function updateLayoutEffect(create: () => (() => void) | void,deps: Array<mixed> | void | null): void {return updateEffectImpl(UpdateEffect, HookLayout, create, deps)}function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {const hook = updateWorkInProgressHook()const nextDeps = deps === undefined ? null : depslet destroy = undefinedif (currentHook !== null) {const prevEffect = currentHook.memoizedStatedestroy = prevEffect.destroyif (nextDeps !== null) {const prevDeps = prevEffect.depsif (areHookInputsEqual(nextDeps, prevDeps)) {hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps)return}}}currentlyRenderingFiber.flags |= fiberFlagshook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,destroy,nextDeps)}
在 update
阶段,代码逻辑类似 useCallback,通过检查依赖是否变更来决定是否更新缓存的方法。可以看到update
阶段比 mount
阶段多了 destroy
参数,destroy
参数是为了在不需要更新时,保留来自于上一个 effect
方法的 destroy
。如果需要更新,在更新的同时也会打上 HookHasEffect
标记。
function pushEffect(tag, create, destroy, deps) {const effect: Effect = {tag,create,destroy,deps,// Circularnext: (null: any),};let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);if (componentUpdateQueue === null) {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 {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;componentUpdateQueue.lastEffect = effect;}}return effect;}
pushEffect
方法将 useEffect
的create
, destroy
, deps
等组成 effect
对象,以链表的形式存到 fiber.updateQueue
中,以供后续使用。
调用 effect
上一步,通过 useEffect
生成了一份 Effect
实例并将其挂载在 fiber 上。接下来就是在 commit 阶段调用 fiber 上的 effect
。
在官方介绍中提到
赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行。
在commit 阶段对 useEffect
和 useLayoutEffect
进行处理。
对于 useLayoutEffect
,React 会在 commit
阶段重绘页面前调用,而对于 useEffect
会在 commit
页面重绘后对其进行调用。