问题背景
- 技术栈:React + TypeScript + Antd
- 问题描述:不知道兄弟们在使用
Antd
的RangePicker
的时候有没有发现这样的问题:他前后两个时间选择的时候,都会触发onChange
方法。如果我们在onChange
的时候发送请求到后台,会遇到这样的问题:- 选择开始时间的时候,触发了一次请求,但是由于网络问题,这个请求还没有回来
- 我又选择了一个结束时间,又发送了一次请求,但是第二个请求先回来了
- 然后第一个请求才回来,导致图渲染的是选择开始时间的图
- 这种问题称为:竞态问题。我们没有办法保证先发出去的请求一定是先回来的
为了方便理解,放个测试数据的图给兄弟们参考一下
解决方法
解决方法一:禁用(第一个请求回来之前不发第二个请求)
- 具体操作:在别的地方也有遇到这样的问题,有的是可以通过禁用选择状态来控制的,比如上面这个年月日的选择器,我们可以在
loading
状态下给他禁用掉,这样就能防止用户在请求还没回来的时候又发送请求。 - 优点:操作方便,只需要给选择框加个
disabled
就行了 - 缺点:有些组件没有办法用
disabled
禁用,如选择日期的RangePicker
;如果网络比较差的时候,用户体验会比较差。
如果是遇到这种
RangePicker
问题的话,可以考虑基于Antd
再封装一下,让他选完之后才发送请求。我们项目就是这样处理的,相当于把onChange
事件放到了onOpenChange
事件里去处理。
解决方法二:判断(判断当前数据是否需要,如果不是忽略即可)
用 didCancel
来作为放弃的标识,每次执行完 useEffect
之后,就把它设置成 true
状态,代码还是比较简单的,直接看代码应该很好理解
useEffect(() => {
let didCancel = false;
setIsLoading(true);
// api 地址为虚构地址
fetch(`https://datacenter.com/data/${id}`)
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return Promise.reject();
})
.then((fetchedData: DataType) => {
if (!didCancel) {
setData(fetchedData);
}
})
.finally(() => {
setIsLoading(false);
});
return () => {
didCancel = true;
}
}, [id]);
解决方法三:中断请求(通过 AbortController 中断请求)
通过传递 abortController.signal
,可以使用 abortController.abort()
轻松终止请求,上代码
useEffect(() => {
const abortController = new AbortController();
setIsLoading(true);
fetch(`https://datacenter.com/data/${id}`, {
signal: abortController.signal,
})
.then((resp) => {
if (resp.ok) {
return resp.json();
}
return Promise.reject();
})
.then((fetchedData: DataType) => {
setArticle(fetchedData);
})
.catch(() => {
if (abortController.signal.aborted) {
console.log('请求已取消');
} else {
console.error('请求失败');
}
})
.finally(() => {
setIsLoading(false);
});
return () => {
abortController.abort();
};
}, [id]);
总结
解决问题的思路就两种:
- 从根源上解决,不允许连续发送多个请求。但是在网络状态较差的情况下,用户体验比较差
- 放弃之前的请求或者放弃之前请求回来的数据