在vue类型的项目开发中,我们一般都是发起异步请求从服务器获取数据后,根据数组数据使用v-for来动态渲染数据列表。
但是,如果一个请求在pending中,再次发送一个请求,最后导致渲染的list,数据重复,或是错误的问题。
原因,就是多次请求了异步接口,一个接口没有返回,另外一个接口就发出去了。因为,ajax是一个异步操作。导致,在回调的时候,两次请求成功后的回调都会执行。就导致数据,错误了。
什么情况下发生这种现象呢? 譬如下拉滚动加载更多 或是 tab切换。
类似,这种,点击tab标签,根据list数据来渲染列表。
当快速切换tab标签时(可以把调试的网速降低,更容易看到这种情况),导致前一个标签的内容也会显示在第二个标签的内容里。
面对这种多次触发异步请求的处理,常用的解决办法如下:
譬如,表单提交了,就把表单提交按钮disable禁用,不然再次提交。
譬如在微信小程序中使用:
wx.showLoading({
title: '加载中',
mask: true
})
具体就上面这个问题,分析一下。
我在修改前的代码如下:
// 获取列表数据
fetchList () {
this.loading = true
wx.showLoading()
let para = {
curUserId: this.userId,
page: this.page,
customerName: this.keyword,
orderStatus = this.active
}
fetchReportRecordList(para)
.then((res) => {
wx.hideLoading()
const data = res.data
this.total = data.total
this.list = [...this.list, ...data.rows]
this.loading = false
})
.catch(() => {
this.loading = false
})
},
导致的原因就是,标签一的请求发出去了,还没有回来,我就切换到标签二去了。这两个异步操作,都会返回数据,都会执行.then
后面的回调。你可能觉得,在回调执行前把list = [] 不就可以了么? 其实是不行的,因为.then是异步的回调,异步请求发出前的函数,是同步执行的。
那如何解决了?
思路就是,发出请求时做一个标记,结果返回的时候,和标记对比一下,是不是我要的,如果已经不是我要的请求结果了,就直接忽略掉这次的请求结果。
面对上面的这个问题,我只需要在发出请求的时候,把当前tab激活的标签编号记录下来,等请求结果返回的时候,对比一下,看发请求的时候的标签编号,和现在的激活标签是不是同一个。
如果是,就添加到list中去,如果不是,那么这次请求结果就不能用了。直接扔掉请求结果。
改写后的代码:
// 获取列表, fix tab快速切换,一个请求pending时再次发送一个请求,导致的list数据错误的问题
fetchList() {
let that = this // 缓存this,表示当前vue 实例,return funtion中的,this会变了。
return (function (j) { // 接收立即执行函数传递过来的参数
// console.log(j, that.active)
that.loading = true
wx.showLoading()
let para = {
curUserId: that.userId,
page: that.page,
customerName: that.keyword,
orderStatus = that.active
}
fetchReportRecordList(para)
.then((res) => {
wx.hideLoading()
// 如果发出请求的时候的tab id 和 目前处于激活状态的tab id 不一样了,说明,结果已经不能要了,直接放弃。
if(j !== that.active) {
return false
}
const data = res.data
that.total = data.total
// 请求结果可用,就追加到list后面。我这里这个方法,是有滚动到底部,触发加载更多。所有,采用追加在尾部的方式。
that.list = [...that.list, ...data.rows]
that.loading = false
})
.catch(() => {
that.loading = false
})
})(that.active) // 立即执行函数,并传参进去形成闭包
},
添加一个立即执行函数和闭包,把当前激活的tabthat.active
传递进去,用参数j
接收起来。在返回结果的时候,去比较 j 和 that.active。
如果在滚动下来加载中,遇到第一页数据加载了两次,这种类似的。都可以用这种方法解决。就是通过判断页码了。
针对上面的问题,另外一个比较好的主流的解决办法。就是一个tab就定义一个list,不要使用同一个list来装数据。就可以避免上面的那个问题了。
具体的代码如下:
const params = {
rows: [],
page: 0,
status: null,
loading: false,
loaded: false,
customerName: '',
}
export default {
data() {
return {
params: JSON.parse(JSON.stringify(params)),
}
}
}
fetchList() {
let item = this.params
if (item.loading || item.loaded) return false
let page = item.page, rows = item.rows
page++
wx.showLoading()
let para = {
curUserId: this.userId,
page: this.page,
customerName: this.keyword,
orderStatus = this.active
}
fetchReportRecordList(para)
.then((res) => {
wx.hideLoading()
const data = res.data
this.total = data.total
rows = [...rows, ...data.rows]
item.rows = rows
item.page = page
item.loading = false
if (rows.length === res.total) {
item.loaded = true
}
this.params = item
})
.catch(() => {
})
},
那么渲染数据的时候,就采用下面这样:
<div v-for="(item, index) in params.rows" class="card" :key="index" @click="linkToDetial(item)">
{{item.title}}
</div>
对应的,tab切换的时候,原来公用的list = [] 这种方式,也需要改为this.params = JSON.parse(JSON.stringify(params))这种重新赋值。
// 重新获取数据
loadTop() {
// this.list = []
this.params = JSON.parse(JSON.stringify(params))
// this.params = Object.assign({}, params)
this.page = 1
this.fetchList()
},
解决的本质,就是一个列表对应一个list来保存数据,不再公用一个list了。并且通过JSON.parse(JSON.stringify(params))来完成深拷贝,切断引用类型的联系。
另外说一句,Object.assign({}, params) 也可以。