背景
接上一章 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
标记着useEffect
LayoutStatic = 2
标记着useLayoutEffect
-
destroy
: effect 返回的回调函数 -
next
: 下一个可能的 effect
-
unMount 过程
在组件卸载时,执行的顺序和机制与加载和更新时一致,只是在检查到 fiber 被删除时进行操作。
网友评论