欢迎关注我的公众号 睿 Talk
,获取我最新的文章:
一、前言
Race Condition 是开发中经常遇到的问题,比如查询天气的时候,先输入“北京”,再输入“深圳”,这时将发起 2 个请求。很可第一个请求花的时间比第二个请求长,如果不做处理,最终看到的是北京的天气,而不是深圳。本文要讨论的就是如何使用 React Hooks 解决这种问题。
二、场景
假设有如下搜索的场景,当用户输入关键字的时候,系统根据关键字搜索,然后实时显示搜索结果。代码如下:
// 模拟网络请求,可以指定延迟时间
function getData(data, delay) {
return new Promise(resolve => {setTimeout(()=>{resolve(`${data} result`);
}, delay);
})
}
function App() {const [query, setQuery] = useState('react');
const [result, setResult] = useState();
useEffect(() => {const fetchData = async () => {
// 搜索 react1 时(第一个请求),2 秒后返回,其余 500 毫秒后返回
const delay = query === 'react1' ? 2000 : 500;
const result = await getData(query, delay);
setResult(result);
}
fetchData();}, [query]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<div>
result: <span>{result}</span>
</div>
</Fragment>
);
}
当我们输入 react12345
时,可以看到最终的结果是react1 result
,而我们期望看到的结果是react12345 result
。
这现象的原因是更新数据的时候,没有对结果的有效性进行判断,用过期的数据覆盖了最新的数据。
三、解决方案
解决方式很简单,就是在更新数据前判断其有效性,改造一下 useEffect
部分的代码:
useEffect(() => {
// 有效性标识
let didCancel = false;
const fetchData = async () => {
const delay = query === 'react1' ? 2000 : 500;
const result = await getData(query, delay);
// 更新数据前判断有效性
if (!didCancel) {setResult(result);
}
}
fetchData();
return () => {
// query 变更时设置数据失效
didCancel = true;
}
}, [query]);
这里利用了 useEffect
数据清理的特性,当 query 发生变化时,将之前的数据请求设置为失效。
四、更好的方案
上面这种方案虽然解决了问题,但体验并不好。在输入数据的过程中,并不能看到输入过程中返回的结果,只能看到最终的结果。我们期望的效果是输入过程中能实时展示有效的结果。再改造下代码:
// 请求序号
let seqenceId = 0;
// 上一个有效请求的序号
let lastId = 0;
function App() {const [query, setQuery] = useState('react');
const [result, setResult] = useState();
useEffect(() => {const fetchData = async () => {
// 发起一个请求时,序号加 1
const curId = ++seqenceId;
const delay = query === 'react1' ? 2000 : 500;
const result = await getData(query,delay);
// 只展示序号比上一个有效序号大的数据
if (curId > lastId) {setResult(result);
lastId = curId;
} else {console.log(`discard ${result}`);
}
}
fetchData();}, [query]);
return (...);
}
这里引入了 2 个变量,一个变量用来标识当前请求的序号,另一个记录上一个有效请求的序号。只有序号比上一个有效序号大的时候,才展示数据。这样就保证了旧的请求不会覆盖新的请求。
最终的代码可以看这里。
五、总结
本文讨论了开发过程中经常遇到的 Race Condition 问题,结合 React Hooks 给出了 2 种解题思路。一种是只展示最终的结果,隐藏过程结果;另一种是将过程中有效的结果也展示出来。可以根据实际的应用场景按需选用。