美文网首页
怎么渲染后端返回的大量数据而不卡顿?

怎么渲染后端返回的大量数据而不卡顿?

作者: 乐宝呗 | 来源:发表于2023-03-22 16:03 被阅读0次

    1、将数据分组分堆,然后利用 requestAnimationFrame渲染赋值

      1. 我们先写一个函数,将10万条数据进行分堆
      2. 所谓的分堆其实就是一次截取一定长度的数据,比如一次截取10条数据,头一次截取0~9,第二次截取10~19等固定长度的截取。举例原来的数据是:[1,2,3,4,5,6,7],假设我们分堆以后,一堆分3个,那么得到的结果就是二维数组了。即:[ [1,2,3], [4,5,6], [7]]
      3. 然后就遍历这个二维数组,得到每一项的数据,即为每一堆的数据,进而使用定时器一点点、一堆堆赋值渲染即可
    
    1、分组分批分堆函数
    function averageFn(arr) {
      let i = 0; // 1. 从第0个开始截取
      let result = []; // 2. 定义结果,结果是二维数组
      while (i < arr.length) { // 6. 当索引等于或者大于总长度时,即截取完毕
        // 3. 从原始数组的第一项开始遍历
        result.push(arr.slice(i, i + 10)); // 4. 在原有十万条数据上,一次截取10个用于分堆
        i = i + 10; // 5. 这10条数据截取完,再截取下十条数据,以此类推
      }
      return result; // 7. 最后把结果丢出去即可
    }
    
    2、使用requestAnimationFrame去依次赋值渲染
    async plan() {
      this.loading = true;
      const res = await axios.get("http://ashuai.work:10000/bigData");
      this.loading = false;
      // 1. 将大数据量分堆
      let twoDArr = averageFn(res.data.data);
      // 2. 定义一个函数,专门用来做赋值渲染(使用二维数组中的每一项)
      const use2DArrItem = (page) => {
        // 4. 从第一项,取到最后一项
        if (page > twoDArr.length - 1) {
          console.log("每一项都获取完了");
          return;
        }
        // 5. 使用请求动画帧的方式
        requestAnimationFrame(() => {
          // 6. 取出一项,就拼接一项(concat也行)
          this.arr = [...this.arr, ...twoDArr[page]];
          // 7. 这一项搞定,继续下一项
          page = page + 1;
          // 8. 直至完毕(递归调用,注意结束条件)
          use2DArrItem(page);
        });
      };
      // 3. 从二维数组中的第一项,第一堆开始获取并渲染(数组的第一项即索引为0)
      use2DArrItem(0); 
    }
    

    2、前端进行分页展示(太简单这里不说了)

    // 上一页、下一页 修改pageIndex即可
    getShowTableData() { 
       // 获取截取开始索引 
       let begin = (this.pageIndex - 1) * this.pageSize; 
       // 获取截取结束索引
        let end = this.pageIndex * this.pageSize; 
       // 通过索引去截取,从而展示
       this.showTableData = this.allTableData.slice(begin, end); 
    }
    
    

    3、表格滚动触底加载

    这里重点就是我们需要去判断,何时滚动条触底。判断方式主要有两种

    1. scrollTop + clientHeight >= innerHeight
    2. new MutationObserver()去观测
    
    npm install --save el-table-infinite-scroll@1.0.10 // 安装插件,注意版本
    // 使用无限滚动插件
    import elTableInfiniteScroll from 'el-table-infinite-scroll';
    Vue.use(elTableInfiniteScroll);
    
    <template>
      <div class="box">
        <el-table
          v-el-table-infinite-scroll="load"
          height="600"
          :data="tableData"
          border
          style="width: 80%"
          v-loading="loading"
          element-loading-text="数据量太大啦,客官稍后..."
          element-loading-spinner="el-icon-loading"
          element-loading-background="rgba(255, 255, 255, 0.5)"
          :header-cell-style="{
            height: '24px',
            lineHeight: '24px',
            color: '#606266',
            background: '#F5F5F5',
            fontWeight: 'bold',
          }"
        >
          <el-table-column type="index" label="序"></el-table-column>
          <el-table-column prop="id" label="ID"></el-table-column>
          <el-table-column prop="name" label="名字"></el-table-column>
          <el-table-column prop="value" label="对应值"></el-table-column>
        </el-table>
      </div>
    </template>
    
    <script>
    // 分堆函数
    function averageFn(arr) {
      let i = 0;
      let result = [];
      while (i < arr.length) {
        result.push(arr.slice(i, i + 10)); // 一次截取10个用于分堆
        i = i + 10; // 这10个截取完,再准备截取下10个
      }
      return result;
    }
    import axios from "axios";
    export default {
      data() {
        return {
          allTableData: [], // 初始发请求获取所有的数据
          tableData: [], // 要展示的数据
          loading: false
        };
      },
      // 第一步,发请求,获取大量数据,并转成二维数组,分堆分组分块存储
      async created() {
        this.loading = true;
        const res = await axios.get("http://ashuai.work:10000/bigData");
        this.allTableData = averageFn(res.data.data); // 使用分堆函数,存放二维数组
        // this.originalAllTableData = this.allTableData // 也可以存一份原始值,留作备用,都行的
        this.loading = false;
        // 第二步,操作完毕以后,执行触底加载方法
        this.load(); 
      },
      methods: {
        // 初始会执行一次,当然也可以配置,使其不执行
        async load() {
          console.log("自动多次执行之,首次执行会根据高度去计算要执行几次合适");
          // 第五步,触底加载相当于把二维数组的每一项取出来用,取完用完时return停止即可
          if (this.allTableData.length == 0) {
            console.log("没数据啦");
            return;
          }
          // 第三步,加载的时候,把二维数组的第一项取出来,拼接到要展示的表格数据中去
          let arr = this.allTableData[0];
          this.tableData = this.tableData.concat(arr);
          // 第四步,拼接展示以后,再把二维数组的第一项的数据删除即可
          this.allTableData.shift();
        },
      },
    };
    </script>
    

    4、 使用无限加载/虚拟列表进行展示

    - 所谓的虚拟列表实际上是前端障眼法的一种表现形式。
    - 看到的好像所有的数据都渲染了,实际上只渲染可视区域的部分罢了
    - 有点像我们看电影,我们看的话,是在一块电影屏幕上,一秒一秒的看(不停的放映)
    - 但是实际上电影有俩小时,如果把两个小时的电影都铺开的话,那得需要多少块电影屏幕呢?
    - 同理,如果10万条数据都渲染,那得需要多少dom节点元素呢?
    - 所以我们只给用户看,他当下能看到的
    - 如果用户要快进或快退(下拉滚动条或者上拉滚动条)
    - 再把对应的内容呈现在电影屏幕上(呈现在可视区域内)
    - 这样就实现了看着像是所有的dom元素每一条数据都有渲染的障眼法效果了
    
    <template>
      <!-- 虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据
                比如窗口只能看到10条数据,一条40像素,10条400像素
                故,窗口的高度为400像素,注意要开定位和滚动条 -->
      <div
        class="virtualListWrap"
        ref="virtualListWrap"
        @scroll="handleScroll"
        :style="{ height: itemHeight * count + 'px' }"
      >
        <!-- 占位dom元素,其高度为所有的数据的总高度 -->
        <div
          class="placeholderDom"
          :style="{ height: allListData.length * itemHeight + 'px' }"
        ></div>
        <!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
        <div class="contentList" :style="{ top: topVal }">
          <!-- 每一条(项)数据 -->
          <div
            v-for="(item, index) in showListData"
            :key="index"
            class="itemClass"
            :style="{ height: itemHeight + 'px' }"
          >
            {{ item.name }}
          </div>
        </div>
        <!-- 加载中部分 -->
        <div class="loadingBox" v-show="loading">
          <i class="el-icon-loading"></i>
          &nbsp;&nbsp;<span>loading...</span>
        </div>
      </div>
    </template>
    <script>
    import axios from "axios";
    export default {
      data() {
        return {
          allListData: [], // 所有的数据,比如这个数组存放了十万条数据
          itemHeight: 40, // 每一条(项)的高度,比如40像素
          count: 10, // 一屏展示几条数据
          start: 0, // 开始位置的索引
          end: 10, // 结束位置的索引
          topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动
          loading: false,
        };
      },
      computed: {
        // 从所有的数据allListData中截取需要展示的数据showListData
        showListData: function () {
          return this.allListData.slice(this.start, this.end);
        },
      },
      async created() {
        this.loading = true;
        const res = await axios.get("http://ashuai.work:10000/bigData");
        this.allListData = res.data.data;
        this.loading = false;
      },
      methods: {
        // 滚动这里可以加上节流,减少触发频次
        handleScroll() {
          /**
           * 获取在垂直方向上,滚动条滚动了多少像素距离Element.scrollTop
           *
           * 滚动的距离除以每一项的高度,即为滚动到了多少项,当然,要取个整数
           * 例:滚动4米,一步长0.8米,滚动到第几步,4/0.8 = 第5步(取整好计算)
           *
           * 又因为我们一次要展示10项,所以知道了起始位置项,再加上结束位置项,
           * 就能得出区间了【起始位置, 起始位置 + size项数】==【起始位置, 结束位置】
           * */
          const scrollTop = this.$refs.virtualListWrap.scrollTop;
          this.start = Math.floor(scrollTop / this.itemHeight);
          this.end = this.start + this.count;
          /**
           * 动态更改定位的top值,确保联动,动态展示相应内容
           * */
          this.topVal = this.$refs.virtualListWrap.scrollTop + "px";
        },
      },
    };
    </script>
    <style scoped lang="less">
    // 虚拟列表容器盒子
    .virtualListWrap {
      box-sizing: border-box;
      width: 240px;
      border: solid 1px #000000;
      // 开启滚动条
      overflow-y: auto;
      // 开启相对定位
      position: relative;
      .contentList {
        width: 100%;
        height: auto;
        // 搭配使用绝对定位
        position: absolute;
        top: 0;
        left: 0;
        .itemClass {
          box-sizing: border-box;
          width: 100%;
          height: 40px;
          line-height: 40px;
          text-align: center;
        }
        // 奇偶行改一个颜色
        .itemClass:nth-child(even) {
          background: #c7edcc;
        }
        .itemClass:nth-child(odd) {
          background: pink;
        }
      }
      .loadingBox {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(255, 255, 255, 0.64);
        color: green;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    }
    </style>
    

    也可以使用vxetable插件实现虚拟列表

    npm i xe-utils vxe-table@3.6.11 --save // 安装依赖
    // 使用VXETable
    import VXETable from 'vxe-table'
    import 'vxe-table/lib/style.css'
    Vue.use(VXETable)
    
    <template>
      <div class="box">
        <vxe-table
          border
          show-overflow
          ref="xTable1"
          height="300"
          :row-config="{ isHover: true }"
          :loading="loading"
        >
          <vxe-column type="seq"></vxe-column>
          <vxe-column field="id" title="ID"></vxe-column>
          <vxe-column field="name" title="名字"></vxe-column>
          <vxe-column field="value" title="对应值"></vxe-column>
        </vxe-table>
      </div>
    </template>
    
    <script>
    import axios from "axios";
    export default {
      data() {
        return {
          loading: false,
        };
      },
      async created() {
        this.loading = true;
        const res = await axios.get("http://ashuai.work:10000/bigData");
        this.loading = false;
        this.render(res.data.data);
      },
      methods: {
        render(data) {
          this.$nextTick(() => {
            const $table = this.$refs.xTable1;
            $table.loadData(data);
          });
        },
      },
    };
    </script>
    
    

    这里有更详细的介绍,链接:https://juejin.cn/post/7205101745936416829

    相关文章

      网友评论

          本文标题:怎么渲染后端返回的大量数据而不卡顿?

          本文链接:https://www.haomeiwen.com/subject/fyfbrdtx.html