useEffect源码解析
它们在渲染时被创建,但是在浏览器绘制后运行。
如果给出了销毁指令,它们将在下一次绘制前被销毁。
它们会按照定义的顺序被运行。
于是就应该有另一个队列来保存这些 effect hook,并且还要能够在绘制后被定位到。
通常来说,应该是 fiber 保存包含了 effect 节点的队列。
每个 effect 节点都是一个不同的类型,并能在适当的状态下被定位到:
在修改之前调用 getSnapshotBeforeUpdate() 实例。
运行所有插入、更新、删除和 ref 的卸载。
运行所有生命周期函数和 ref 回调函数。
生命周期函数会在一个独立的通道中运行,所以整个组件树中所有的替换、更新、删除都会被调用。
这个过程还会触发任何特定于渲染器的初始 effect hook。
useEffect() hook 调度的 effect —— 也被称为“被动 effect”,
它基于这部分代码。
hook effect 将会被保存在 fiber 一个称为 updateQueue 的属性上,
每个 effect 节点都有如下的结构:
tag —— 一个二进制数字,它控制了 effect 节点的行为
create —— 绘制之后运行的回调函数
destroy —— 它是 create() 返回的回调函数,将会在初始渲染前运行
inputs —— 一个集合,该集合中的值将会决定一个 effect 节点是否应该被销毁或者重新创建
next —— 它指向下一个定义在函数组件中的 effect 节点
除了 tag 属性,其他的属性都很简明易懂。
如果你对 hook 很了解,你应该知道,React 提供了一些特殊的 effect hook:
比如 useMutationEffect() 和 useLayoutEffect()。
这两个 effect hook 内部都使用了 useEffect(),
实际上这就意味着它们创建了 effect hook,
但是却使用了不同的 tag 属性值。这个 tag 属性值是由二进制的值组合而成(详见源码):
const NoEffect = /* */ 0b00000000;
const UnmountSnapshot = /* */ 0b00000010;
const UnmountMutation = /* */ 0b00000100;
const MountMutation = /* */ 0b00001000;
const UnmountLayout = /* */ 0b00010000;
const MountLayout = /* */ 0b00100000;
const MountPassive = /* */ 0b01000000;
const UnmountPassive = /* */ 0b10000000;
React 支持的 hook effect 类型 这些二进制值中最常用的情景是使用管道符号(|)连接,
将比特相加到单个某值上。然后我们就可以使用符号(&)检查某个 tag 属性是否能触发一个特定的行为。
如果结果是非零的,就表示可以。
const effectTag = MountPassive | UnmountPassive
assert(effectTag, 0b11000000)
assert(effectTag & MountPassive, 0b10000000)
如何使用 React 的二进制设计模式的示例 这里是 React 支持的 hook effect,
以及它们的 tag 属性(详见源码):
Default effect —— UnmountPassive | MountPassive.
Mutation effect —— UnmountSnapshot | MountMutation.
Layout effect —— UnmountMutation | MountLayout.
以及这里是 React 如何检查行为触发的(详见源码):
if ((effect.tag & unmountTag) !== NoHookEffect) {
// Unmount
}
if ((effect.tag & mountTag) !== NoHookEffect) {
// Mount
}
所以,基于我们刚才学习的关于 effect hook 的知识,
我们可以实际操作,从外部向 fiber 插入一些 effect:
function injectEffect(fiber) {
const lastEffect = fiber.updateQueue.lastEffect
const destroyEffect = () => {
console.log('on destroy')
}
const createEffect = () => {
console.log('on create')
return destroy
}
const injectedEffect = {
tag: 0b11000000,
next: lastEffect.next,
create: createEffect,
destroy: destroyEffect,
inputs: [createEffect],
}
lastEffect.next = injectedEffect
}
const ParentComponent = (
<ChildComponent ref={injectEffect} />
)