背景
接上一章 React 源码探源 4 useState,来研究一下 useEffect 与 useLayoutEffect 相关的实现细节。
相关定义
先来看一下 React 相关的官方文档
-
useEffect,
- 作用:此 hook 主要是用来在组件渲染完成以后执行一些称为 effect 的操作,例如发送 ajax 请求,打点,当一些 state 发生变化以后,再更新其它的 state 等。
- 执行时机:
useEffect实在下次渲染之前执行,执行时浏览器已经对上次状态更新渲染完成。 - 返回值:
useEffect可以返回一个回调函数,当组件unMount时,会被调用。
-
useLayoutEffect
- 作用:如文档所示,绝大部分情况下都推荐使用
useEffect,只有使用useEffect的结果有些怪异时才会使用这个 hook。 笔者根据实验发现,只有更改 DOM 时导致了一些抖动的行为时使用useLayoutEffect时才会派上用场。 - 执行时机:
useLayoutEffect执行时,浏览器还未对 DOM 进行渲染。可以获取新的 DOM 进行操作。执行的时机较useEffect更早。 - 返回值:
useLayoutEffect也可以返回一个回调函数,也会在unMount时被调用。调用的时机也会较useEffect的回调更早。
- 作用:如文档所示,绝大部分情况下都推荐使用
示例代码
本次实例使用的详细代码如下
function Dev() {
const [count, setCount] = React.useState(0);
React.useEffect(function effectCb() {
console.log('in effect');
if (count === 1) {
setCount(10);
}
return function effectUnMount() {
console.log('effect unmount');
};
}, [count]);
React.useLayoutEffect(function layoutEffectCb() {
console.log('in layout effect', count);
return function layoutEffectUnMount() {
console.log('layout effect unmount');
};
}, [count]);
return (<div id="div">
<button id="btn" onClick={() => {
setCount(function add(c) {return c + 1;});
}}>click me</button>
<div>the new text is <span>{count}</span></div>
</div>);
}
详细流程

useEffect 和 useLayoutEffect 的详细流程
render
- 在执行
useEffect,useLayoutEffect时会将对应的回调存储起来,详细结构参加下部分 - 更新时,会检查对应的
dependency是不是有变化再决定是否将effect加进来。
commit
-
useEffect的列表会先被检查,如果有更新,会使用MessageChannel.postMessage计划在下次 eventLoop 执行。从代码注释中看到,这样的执行方式比setTimeout要好,因为setTimeout至少有 4ms 的延迟。 -
useLayoutEffect会在commitMutationEffects,也就是内存中的 DOM 更新以后马上执行,执行的时机比useEffect更早。 - effect 执行时,会检查 fiber 的
updateQueue中是否含有对应类型的 effect,并将它顺序执行。 - 执行过程类似与
useState的调用类似。- 通过
subtreeFlags来检查子节点是否有 effect 需要执行。 - 通过
flags检查当前节点有 effect 需要执行。
- 通过
effect 数据结构
可以看到以下的信息:
-
useLayoutEffect,useEffect生成的 hook 会跟useState生成的 hook 一起存储在 fiber 的memorizedState下面的链表里面。 - fiber 的
updateQueue使用lastEffect存储着所有的 effect 生成的循环链表。 - hook 中的
memorizedState和lastEffect指向相同的地方,存储着 effect 的相关信息-
create: effect 的回调函数 -
tag: 存储着effect的类型Passive = 4标记着useEffectLayoutStatic = 2标记着useLayoutEffect -
destroy: effect 返回的回调函数 -
next: 下一个可能的 effect
-
unMount 过程
在组件卸载时,执行的顺序和机制与加载和更新时一致,只是在检查到 fiber 被删除时进行操作。