开头
这一篇主要是 useEffect的细节分析整理。因为 hooks 部分有点长也就分开了。关于 useState的部分可以看上一篇。这里入口函数的分析也在上一篇里面,如果有不明需要返回参考。
useEffect 细节
在常见的hooks 函数里面useEffect使用频度和 useState 是不相上下的。
使用场景
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react';
export default () => { const [text, setText] = useState('hello'); useEffect(() => { console.log('Hello World'); }, [text]); return <div onClick={() => setText('Hello')}>click</div> }
|
以上是一个非常常见且典型的useEffect使用场景。
在这个使用例子中,useEffect 传入了一个函数作为第一个参数,传入一个state数组做为第二个参数。
细节
我们承袭前一片的分析,可以得知,当初次mount时候,useEffect 指向mountEffect函数,当 update 的时候,则指向updateEffect函数。
mountEffect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { if (__DEV__) { }
if (__DEV__ && enableDoubleInvokingEffects) { } else { return mountEffectImpl( PassiveEffect | PassiveStaticEffect, HookPassive, create, deps, ); } }
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; currentlyRenderingFiber.flags |= fiberFlags; hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, undefined, nextDeps, ); }
|
这里可以看出,在源码中,我们传入的函数称为 create,数组则称为 deps。
这里函数里面涉及到一些位运算的 flag,这里暂且略过对核心路径进行解读。
当这一路调用下来,通过mountWorkInProgressHook函数,它得到一个workInProgressHook,并在其memoizedState属性上挂载了一个新的空白Hook 实例。
接下来就是pushEffect函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function pushEffect(tag, create, destroy, deps) { const effect: Effect = { tag, create, destroy, deps, next: (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; }
|
如果前面对 useState那篇还有印象的话,大致可以看到这里主要是对updateQueue属性的处理,而 useState 则主要是对queue属性的处理。
这里还是简述一下过程
- 当 hook上updateQueue属性没有有效值时候,创建一个空 updateQueue,并将它的lastEffect属性设置为由之前传入的 create 和 deps 构成的 Effect 实例,这个 Effect 是一个循环链表,这里将首尾都链到它自己。
- 当有有效值时候将之前传入的 create 和 deps 构成的 Effect 实例,塞到firstEffect前面,并将它本身设为firstEffect。
updateEffect
这里看一下细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| function updateEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { if (__DEV__) { } return updateEffectImpl(PassiveEffect, HookPassive, create, deps); }
function updateEffectImpl(fiberFlags, hookFlags, create, deps): 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)) { pushEffect(hookFlags, create, destroy, nextDeps); return; } } }
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, destroy, nextDeps, ); }
|
这里很显然就又回到了pushEffect,这里分支就是之前提到的
当有有效值时候将之前传入的 create 和 deps 构成的 Effect 实例,塞到firstEffect前面,并将它本身设为firstEffect。
这里处理的核心,归根结底,还是在于updateQueue属性。
PS: 当我写完这篇之后,自己再看一遍其实也还是感觉言之未尽。但是hooks这个机制呢,它本身说到底,其实还是针对函数组件中的各种状态进行一个临时的状态保存。
这里的保存,有闭包,也有直接的在属性上进行挂载。处理完毕这些之后在后续的更新里面对它进行读取、处理,并反馈到 View 上(说穿了其实有些兴味索然。。。)。
后续
pushEffect函数处理了hook.updateQueue的赋值,并将生成的 effect 赋值给到hook.memoizedState。
这里没有像 useState 那里那样,还有过一次直接计算和直接触发更新。究其根本,useEffect 在 mount 之后,并不一定真的会执行这个 create 函数,他就像一个订阅器,当我们执行了 setXXX(‘new Val’)之后,通过对应的 setXXX开始更新流程,并在这个流程中对比 deps,并针对性的执行 create。
然而这个流程,就是更新层面的逻辑了。回头看看很久之前分析过的update 环节,实在有些怅然。
《react-v16-Update renderPhase篇》
《react-v16-Update-commitPhase》
hooks 部分写到这里实在有些兴味索然,就这样做个收尾吧。这里的收获是,知道 fiber 节点上一些属性(updateQueue|memoizedState|pending之类),是如何变化、反馈到View层面的。