文章目录
- useEffect 和 useLayoutEffect 的源码解读
- useEffect
- 源码解读
- mountEffectImpl
- updateEffectImpl
- useLayOutEffect
- 源码解读
- mountLayoutEffect
- updateLayoutEffect
- 总结
useEffect 和 useLayoutEffect 的源码解读
useEffect
文件在 packages/react-reconciler/src/ReactFiberHooks.old.js
源码解读
根据上一篇 useState 的经验可知,useEffect 在 mount 的时候,会调用 mountEffectImpl 函数,在 update 的时候,会调用 updateEffectImpl 函数
mountEffectImpl
-
首先调用 mountWorkInProgressHook 获取当前 hook对应的数据
-
获取 effect 对应的 依赖项
-
为当前 fiber 的 flags 增加上 该hook的 fiberFlags(这个 fiberFlags 对于 useEffect 和 useLauoutEffect 是不同的,所以用于区分这两者)
-
将pushEffect函数返回的 新的 effect 赋值给 hook.memoizedState
(注意:对于 useState 来说,memoizedState 保存的是状态,而 useEffect 来说,memoizedState 实际上是保存的 当前hook上最后一个 effect 数据)
pushEffect
判断 componentUpdateQueue 是否存在,若存在,就为这个环状链表附加一个 effect;若不存在,就新建一个环状链表
-
首先会创建一个变量 componentUpdateQueue,并被赋值给 currentlyRenderingFiber.updateQueue
updateQueue 的数据结构是:
-
若 componentUpdateQueue 不存在,把创建的数据结构 effect 挂载到 componentUpdateQueue.lastEffect 上,并且effect 与自己形成一条环状链表(与 update 的时候,数据结构类似)
-
effect数据结构:(结合 useEffect 的API,判断以下)
- create :是 useEfect 的回调函数
- destroy:useEfect 的回调函数的返回值的 那个函数
-
若 componentUpdateQueue 存在,则在环状链表后面新加上一个 effect
-
最终返回本次创建的 effect
updateEffectImpl
useEffect 在 mount 和 update 阶段有什么区别吗?
在 update时,前一个 useEffect 已经被执行过了,所以说前一个 useEffect 可能存在他的 destory 函数
- 从currentHook.memoizedState中取出 上一次 更新的 effect,即 prevEffect
- 再从 prevEffect 中取出 destory 函数(注意:需要上一次 effect 中的 create 执行完之后 ,才会有 上一次 effect 的 destory 函数)
- 判断 上一次的 deps 和 这一次的 deps 比较是否相等:不论是否相等,都会pushEffect,只是区别在于一个 hookHasEffect 参数。若相等,在 pushEffect 函数的第一个参数是直接传一个 hookFlags 参数,若不相等,是传的 HookHasEffect | hookFlags
疑惑:
为什么要在 deps 没有改变的情况下,还要 pushEffect 呢?
解答:
在 函数组件中,所有 hook 是保存在 fiber.memoizedState 上,并且会形成一个单向的环状链表,这些 hooks 的调用顺序都是不变的,同样,对于 useEffect 和 useLayoutEffect,也是保存在一条单向链表上的,顺序也不能改变。所以即使本次更新 deps 没有改变,不会调用 hooks 对应的回调函数,我们也需要 pushEffect,确保hooks的环状链表在整个更新中都是能对应的上的
useLayOutEffect
源码解读
在 mount 阶段时,调用 mountLayoutEffect 函数,在 update 阶段时,调用 updateLayoutEffect
mountLayoutEffect
可以看出 mountLayoutEffect 最终也是调用 mountEffectImpl 函数,只不过 传入的第二个参数是 HookLayout
updateLayoutEffect
和 mountLayoutEffect 同理,也是调用的 updateEffectImpl, 传入的第二个参数是 HookLayout
总结
所以 useEffect 和 useLayoutEffect 他的整体运行流程基本一致,唯一不同的是 在 commit 阶段会在不同的时机 调用