移动端瀑布流v-infinite-scroll渲染乱序问题

最近在做移动端瀑布流效果,使用v-infinite-scroll的时候遇到了渲染乱序的问题

在移动端的产品列表页中先请求第一页的数据进行渲染 后续再随着用户滑动请求接下来页码的数据,首先在相应div上使用了v-infinite-sroll 定义load函数

<div
  v-infinite-scroll="load"
  infinite-scroll-distance="100"
>
  <div
    v-for="data in dataList"
    :key="data.id"
  >
  ……
  </div>
</div>

<script>
export default {
  data {
    return {
      dataList: [] // 用于渲染的数据
      ……
    }
  },
  async mounted() {
    this.reqMsg.current = 1 // 先请求第一页数据 
    let res = await getDataList(this.$axios, this.reqMsg) 
    if(res.status == 200) { // 请求成功
      this.handleData(res.dataList) // 进行数据处理
    }
  },
  methods: {
    handleData(newDataList) {
      // 渲染前进行数据处理
      let dataList = this.dataList // 接收旧数据
      newDataList.forEach(item => {
        dataList .push(item) // 追加新数据
      })
      …… 
      this.dataList = dataList // 赋值
    },
    // 每次距离底部100px时触发加载函数
    load() {
      this.reqMsg.current = this.reqMsg.current + 1  // 请求页码加1
      let res = await getDataList(this.$axios, this.reqMsg) 
      if(res.status == 200) { // 请求成功
        this.handleData(res.dataList) // 进行数据处理
      }
    }
  }
}
</script>

 通过以上逻辑就可以简单实现移动端瀑布流效果,效果就是进入页面先加载第一页数据,随用户滚动行为再陆续请求后面页码的数据一直到请求完,新请求的数据会在原来渲染的基础上继续渲染新页码的数据。

但是在应用到大数据量的瀑布流时发现了一个问题,就是渲染不是按照页码进行的,有时候最后一页的数据会渲染在前面的位置而不是页面底部,记录到的有一次渲染是按照1、2、3、4、5、6、7、10、8、9进行的。于是开始重新检查代码逻辑,只找到了一种解释,由于滑动屏幕速度很快,load函数在短时间内被连续触发,但是响应的速度是没办法控制的,这跟很多因素都关联,就会造成有时候后面页码的数据先响应回来了,就比如例子中的第10页数据,这时关于第10页的load函数执行就会接着往下走,进入数据处理函数,由于第10页的数据先被处理好赋值到了this.dataList,数据改变驱动视图更新,此时页面上第10页的数据就会被追加呈现在第7页数据的后面,导致了渲染没有按照我们想要的效果呈现。

那么如何解决这一问题呢,首先清楚我们是没办法控制响应顺序的,就算请求是按次序发送的,响应也不一定,那么只能在数据处理的环节进行控制,赋值的时候保证数据的排序,那么渲染也能保证顺序了。代码如下:

<div
  v-infinite-scroll="load"
  infinite-scroll-distance="100"
>
  <div
    v-for="data in dataList"
    :key="data.id"
  >
  ……
  </div>
</div>

<script>
export default {
  data {
    return {
      dataList: [] // 用于渲染的数据,
      currentRenderPage: 2; // 表明当前等待渲染的页码
      responseDataList: []; // 用于保存提前响应回来的数据
      ……
    }
  },

  async mounted() {
    this.reqMsg.current = 1 // 先请求第一页数据 
    let res = await getDataList(this.$axios, this.reqMsg) 
    if(res.status == 200) { // 请求成功
      this.handleData(res.dataList) // 进行数据处理
    }
  },

  methods: {
    // 每次距离底部100px时触发加载函数
    load() {
      this.reqMsg.current = this.reqMsg.current + 1  // 请求页码加1
      let res = await getDataList(this.$axios, this.reqMsg) 
      if(res.status == 200) { // 请求成功
        // 假设后端返回数据包含当前响应的页码 如res.current,则直接传参
        // 如果没有用请求参数的current即可 只要能代表当前响应的页码就可以
        // 假设后端返回数据包含总页数,如res.pages,则直接传参
        this.handleData(res.dataList, res.current, res.pages) // 进行数据处理
      }
    },
    // 传入当前响应的页码 表明正在处理第几页数据
    handleData(newDataList, responsePage, totalPages) {
      if(responsePage != undefined) {
        // 表明从laod函数而来
        let dataList = this.dataList // 接收旧数据
        if(this.currentRenderPage == responsePage) {
          // 表明当前等待渲染的页码和响应过来的一致 直接追加渲染
          newDataList.forEach(item => {
            dataList .push(item) // 追加新数据
          })
        } else {
          // 表明当前响应页码大于当前等待渲染的页码
          // 当前等待渲染页码小于等于总页数 大于总页数的页码就不需要等待渲染了
          if(this.currentRenderPage <= totalPages) {
            let responseDataList = this.responseDataList // 接收之前存的提前响应回来的数据
            if(newDataList.length > 0) { // 响应的数据非空
              // 将提前响应回来的数据存起来 记录响应页码
              responseDataList.push({
                index: responsePage,
                list: newDataList
              })
            }
            if(responseDataList.length > 0) {
              let currentDataList = [] // 保存从responseDataList中取出来的数据
              let deleteIndex = -1 // 保存要取出来的数据对应的索引 取出来以后进行删除
              responseDataList.forEach((item,i) => {
                // 如果responseDataList有等待渲染页面的数据
                if(item.index == this.currentRenderPage) {
                  currentDataList = item.list
                  deleteIndex = i
                }
              })
              currentDataList.forEach((item,index) => {
                dataList.push(item) // 追加新数据
              })
            }
            if(deleteIndex != -1) { // 表明已经取到了要渲染的数据
              this.currentRenderPage = this.currentRenderPage + 1 // 继续等待下一页的数据
              responseDataList.splice(deleteIndex, 1) // 取到了以后删掉
            }
            this.responseDataList = responseDataList // 更新responseDataList数组
          }
        }
      } else {
        // 首页处理数据时未传参按照原处理逻辑即可
        let dataList = this.dataList // 接收旧数据
        newDataList.forEach(item => {
          dataList .push(item) // 追加新数据
        })
        …… 
        this.dataList = dataList // 赋值
      }
    }
  }
}
</script>

 总体思想就是等待正确页码的数据,如果响应本来就是正确的直接赋值触发渲染,如果不是则先暂存,同时从以往暂存过的数据里查找是否有正确页码的数据,有的话赋值触发渲染,没有则结束。在一遍遍中触发执行中就能保证渲染是按照页码顺序的。

实在也没想到更好的方法解决,各位大佬们如果有更好的方法欢迎指导!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值