先引用官网给出的例子:
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return function clear() => {
ignore = true;
};
}, [userId]);
假设在组件的使用过程中,外部传入的 props 参数id
,改变了两次,第一次传入 id: 1, 第二次传入 id: 2
那么我们来梳理一下整个过程:
1.传入props.id = 1
2.组件渲染
3.DOM 渲染完成,副作用逻辑执行,返回清除副作用函数clear,
命名为clear1
4.传入props.id = 2
5.组件渲染
6.组件渲染完成,clear1 执行(注意此时 id 为 1 的请求结果还没有返回)
7.副作用逻辑执行(此时 clear1 执行后,ignore 变为 true,id 为 1 的请求还在 await,但因为 ignore 为 true 了,所以不会执行 setState,而是执行当前渲染的副作用函数内的请求,即 id 为 2,ignore 被重新赋值为 false),返回另一个 clear 函数,命名为clear2
8.组件销毁,clear2 执行
关键的地方就在于 clear 函数的执行:
- 每次副作用的执行都会返回一个新的清除副作用的函数
- 清除函数会在下一个副作用逻辑之前执行(DOM 渲染完成之后)
- 组件销毁也会执行一次
进一步思考,clear1 执行的时候,访问了 props.id
,那么这个 id 值是 1 还是 2 呢?
因为 useEffect 返回的是个函数,在执行时产生了一个闭包,根据闭包的相关定义,返回的 clear 函数能访问自身作用域外的变量,当组件第一次渲染时传入 id 是 1,此时的 clear 函数中的props.id
值为 1。
通过 useEffect 的 clear 函数可以解决竞态问题(race conditions)