使用vue开发项目的时候,异步请求数据,连续触发多次请求导致的渲染的数据列表数据错误的问题解决办法。

35 篇文章 1 订阅
21 篇文章 0 订阅

问题描述:

在vue类型的项目开发中,我们一般都是发起异步请求从服务器获取数据后,根据数组数据使用v-for来动态渲染数据列表。
但是,如果一个请求在pending中,再次发送一个请求,最后导致渲染的list,数据重复,或是错误的问题。

原因,就是多次请求了异步接口,一个接口没有返回,另外一个接口就发出去了。因为,ajax是一个异步操作。导致,在回调的时候,两次请求成功后的回调都会执行。就导致数据,错误了。

什么情况下发生这种现象呢? 譬如下拉滚动加载更多 或是 tab切换。
在这里插入图片描述
类似,这种,点击tab标签,根据list数据来渲染列表。

当快速切换tab标签时(可以把调试的网速降低,更容易看到这种情况),导致前一个标签的内容也会显示在第二个标签的内容里。

怎么解决呢?

面对这种多次触发异步请求的处理,常用的解决办法如下:

1、在请求发出后,处于loading状态时,禁用再次触发异步的操作按钮。

譬如,表单提交了,就把表单提交按钮disable禁用,不然再次提交。

2、请求发出后,处于loading状态是,显示mask遮罩,不然点击下面的其他的操作。

譬如在微信小程序中使用:

    wx.showLoading({
     title: '加载中',
     mask: true
 })

3、终极大招,使用闭包

具体就上面这个问题,分析一下。
我在修改前的代码如下:

// 获取列表数据
     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。

如果在滚动下来加载中,遇到第一页数据加载了两次,这种类似的。都可以用这种方法解决。就是通过判断页码了。

4、使用JSON.parse(JSON.stringify(params))来深拷贝,切断引用,渲染列表数据时,不用同一个list来渲染。

针对上面的问题,另外一个比较好的主流的解决办法。就是一个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) 也可以。

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值