乐趣区

关于vue.js:分片渲染Vue虚拟列表

问题

以前在面试中遇到过一个这样的问题:如果给你数据量很大比方十万条,后盾又没有分页的性能,那么你怎么操作能加重浏览器的压力或者说怎么能更晦涩的渲染进去。

很显著这个问题属于那种没事找事型的,那么既然问进去了,咱们还是得探一探到底。

1. 分片渲染

分片渲染顾名思义就是数据离开渲染,这样解决了一次性渲染所带来的卡顿。

实现原理

依据 EventLoop 原理实现的,代码执行放入执行栈中,同步代码先执行,遇到宏工作放入宏工作队列,遇到微工作放到微工作队列,等同步代码执行结束之后清空微工作队列,而后再将宏工作队列的第一项放入执行栈中,再进行以上的执行过程,这就造成了事件环机制(每循环一次会执行一个宏工作,并清空对应的微工作队列)

代码实现
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title> 分片渲染 </title>
  </head>
  <style>
    #app {
      width: 100px;
      height: 300px;
      overflow: auto;
    }
  </style>
  <body>
    <div id="app"></div>
  </body>
</html>
<script>
  let index = 0;
  let total = 100;
  let id = 0;
  function load() {
    index += 10;
    if (index <= total) {
    // 定时器属于宏工作,它的执行程序是等上一个宏工作也就是上一个定时器执行实现之后能力执行
      setTimeout(() => {for (let i = 0; i < 10; i++) {let item = document.createElement("div");
          item.innerHTML = id++;
          app.appendChild(item);
        }
    // 应用递归的形式
        load();}, 1000);
    }
  }
  load();
</script>

下面也说到了分片渲染比一次性渲染的性能更好,然而 dom 元素还是那么多,本质上并不是最优的解决办法,那么咱们来看一看虚构列表。

2.vue 虚构列表



由下面两张图片能够看出虚构列表就是只显示滚动条对应视口的相应数据。

实现虚构列表的思路

1. 因为虚构列表只显示视口的数据,所以不会显示出滚动条,那么咱们须要造一个滚动条。
2. 造出滚动条之后元素就会被挤到上面 咱们须要设置一个绝对于父元素的定位。
3. 因为设置了定位,当父元素滚动的时候,设置了定位的子元素会绝对应的挪动,那么咱们须要在滚动的时候给子元素动静的设置 top 的值
4. 获取到数据之后,须要依据视口来筛选要显示的数据

代码实现

这个 demo 是应用 vue-cli 生成的脚手架。
home.vue

// size: 每一个 item 的高度   remain: 要显示几个  items: 获取到的数组
<template>
  <div id="home">
    <virtual-list :size='40' :remain='8' :items="items"></virtual-list>
  </div>
</template>
<script>
import VirtualList from "../components/VirtualList.vue";
export default {
  components: {"virtual-list": VirtualList,},
  data() {
    return {items: [],
    };
  },
  created() {for (let i = 0; i < 10000; i++) {this.items.push({ name: "我是第" + i + "个", id: i});
    }
  },
};
</script>

virtualList.vue

<template>
  <div id="VirtualList" ref="VirtualList" @scroll="handleScroll">
    // 滚动条显示
    <div :style="{height: items.length * size +'px'}"></div>
    <div id="container" ref="container" :style="{top: offsetTop}">
      <div
        v-for="(v, i) in itemList"
        :key="i"
        class="item"
        :style="{height: size +'px'}"
      >
        {{v.name}}
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {size: { type: Number},
    remain: {type: Number},
    items: {type: Array},
  },
  computed: {itemList: function () {return this.items.slice(this.start, this.end);
    },
  },
  data() {
    return {
      start: 0,
      end: this.remain,
      offsetTop: 0,
    };
  },
  methods: {handleScroll() {
        // 从第几个 item 开始显示
      this.start = this.$refs.VirtualList.scrollTop / this.size;
        // 开始地位加上要显示几个 = end
      this.end = this.start + this.remain;
        // 动静更改定位的 top 值,保障不会随父元素一起滚动
      this.offsetTop = this.$refs.VirtualList.scrollTop + "px";
    },
  },
  mounted() {this.$refs.VirtualList.style.height = this.size * this.remain + "px";}
};
</script>
<style>
#VirtualList {
  overflow: auto;
  width: 200px;
  position: relative;
}
#container {
  width: 100%;
  position: absolute;
  left: 0;
  top: 0;
}
.item {
  line-height: 40px;
  box-sizing: border-box;
  border-bottom: 1px solid #ccc;
}
</style>
退出移动版