最近在调试一个 react 项目的 bug 时,发现 props 的值会出现不一致的情况,分析了一下原因,发现出现不一致的情况时,错误的 props 传递均是在父组件调用 useEffect() 时传递的,具体来说是子组件调用了父组件的获取数据的方法,父组件自己又调用了一次这个方法,导致错误出现,由此引出这个一不注意就会出现的问题 --竞态(Race Conditions)。
在useEffect中异步获取数据,之后把返回的数据设置给state,这是在 react 中很常见的写法,但是如果接口返回的时间不一样,
就会导致页面上显示的是返回最慢的接口数据,不是最后一次发起请求的数据,下面是模拟的请求数据和场景:
import { useState, useEffect } from 'react';
const Race = () => {
const [data, setData] = useState({});
const [id, setId] = useState(1);
// 模拟数据
const fechFakeData = (id: number): {} => {
// 模拟不同的返回时间
const delayTime = id === 2 ? 2000 : 500;
return new Promise((res) => {
setTimeout(() => {
res({ id, name: `user--${id}` });
}, delayTime);
});
};
useEffect(() => {
const fetchData = async () => {
const response = await fechFakeData(id);
setData(response);
};
fetchData();
}, [id]);
return (
<div>
<p>id:{id}</p>
<p>{JSON.stringify(data)}</p>
<button
onClick={() => {
setId(2);
}}
>
get user 2 data
</button>
<button
onClick={() => {
setId(3);
}}
>
get user 3 data
</button>
</div>
);
};
export default Race;
可以看到,点击 get user 2 data 按钮之后在 2s 之内再去点击 get user 3 data 按钮,数据不一致的情况就出现了,明明 id 为 3,但是显示的确实 id 为 2 的数据,这就是竞态导致的问题
解决方法就是新增一个变量去控制是否去setData:
// ...
useEffect(() => {
// 新增flag控制
let active = true;
const fetchData = async () => {
const response = await fechFakeData(id);
if (active) {
setData(response);
}
};
fetchData();
// id 变化时设置数据失效
// active = false;
return () => {
active = false;
};
}, [id]);
// ...
我们来看一下ahook中 useRequest 是怎么解决这个问题的源码 :
基本原理差不多,就是设置一个
count变量去控制,请求一次就你 count+=1 ,同时赋值给一个常量currentCount, 二者相等的时候才去setState,最后在组件卸载的时候count+=1 不执行最后一次请求