背景
项目所用的前端框架为React,不过下面所讲的场景和React没啥关系,其他前端架构也会有类似问题。
假如说页面中有两项菜单可供选择,这两项菜单下的内容是同一个页面,同一个组件,两项菜单下的页面结构是一样的,只是数据不同,数据比如都是使用 axios 请求 REST API 获取到的,然后用 setState
把 API 传过来的值展示到页面中。
axios.get('/test', {
params: {
type: 'fruit'
}
})
.then(function (res) {
if (res.status === 200) {
this.setState({
data: res.data
}
}
})
项目初次加载和点击菜单选项时都会执行类似于上面的请求 API 的方法。
正常点击切换两项菜单时问题不大,能正确展示数据,当我点击很快时,由于向 API 请求数据需要时间,就可能把某次请求pending,然后 setState
就可能会把比如第一个菜单项下的内容展示到第二个菜单下。
解决方案
按钮点击很快这件事本身可以做处理,但我感觉限流也是权宜之计,比如极端情况,某段时间 API 返回数据挺慢,在限流的时间内还没返回数据,这样 setState
还是有可能把数据展示错乱。关键在于请求 pending 时就切换菜单会出现数据错乱。
如果我们在发新的请求之前能把之前 pending 的请求取消掉就会解决这个问题,这就是 axios 的 CancelToken 所做的事情。
我们在点击菜单项绑定的函数中每次定义下面两个变量
const CancelToken = axios.CancelToken
const source = CancelToken.source()
注意上面两个变量初始化时每次切换菜单项时都要重新定义,不然只定义一次的话,上面两个变量的值只是初始化时的值。我们可以把上面两个变量写到我们发送请求的前面,然后把发送请求包装成一个函数,在 componentDidMount()
和 切换菜单项时调用这个函数。然后完整的代码如下。
handleRequest() {
const CancelToken = axios.CancelToken
const source = CancelToken.source()
axios.get('/test', {
params: {
type: 'fruit'
},
cancelToken: source.token
})
.then(function (res) {
if (res.status === 200) {
this.setState({
data: res.data
}
}
})
.catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
})
}
然后我们在每次点击时都要把之前 pending 的请求取消掉,在点击菜单项绑定的函数里加入下面这条语句:
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
至此,我们就解决了点击切换菜单项太快数据展示错乱的问题。