vue编码之长列表优化

共 11090字,需浏览 23分钟

 ·

2021-06-01 03:49



前端猎手
 链接每一位开发者,让编程更有趣儿!
关注

今天主要想跟大家聊聊长列表优化,有的时候我们需要在页面上显示特别长的列表,这种情况主要发生在移动端或者后台管理的页面中,在移动端往往有个下拉刷新内容的功能,不停地往上翻,到底部后会加载更多内容,这样会导致列表中会有很多元素,从而导致页面的卡顿,由于元素多了以后,浏览器渲染也需要时间,特别是新增了一些元素,也会触发浏览器的重排重绘,因此无论是内存的占用或者GPU的渲染都会给性能带来一些损耗。

举个栗子🌰:

假设我们需要在页面长列表中渲染10000条数据,代码如下:

//APP.vue

<template>
  <div class="app">
    <div class="scroller">
      <Listltem v-for="item in item" :key="item.id" :item="item" />   
    </div>
  </div>

</template>

<script>
import Listltem from "./
components/Listltem.vue";
var item = [];
for (var i = 0; i < 10000;i++) {
  item.push({
    id:i + 1,
    count:i + 1,
  })
}
export default {
 components:{
   Listltem,
 },
  data() {
    return {
      item,
    };
  },
 
};
</script>

<style lang="
less" scoped>

</style>

//组件Listltem.vue

<template>
  <div class="list-container">
    <span>id{{item.id}}</span>
    <span>name{{item.count}}</span>
    <span>age{{item.count}}</span>


  </div>

</template>

<script>
export default {
  props: {
    item:Object,
  },
  data() {
    return {};
  },

};
</
script>

<style lang="less" scoped>
  .list-container{
    margin0 auto;
    width500px;
    text-align: center;
    height54px;
    padding1em;
    display: grid;
    box-sizing: border-box;
    grid-template-columnsrepeat(3,1fr);
    box-shadow0 0 3px rgba(0000.5);
  }
</style>


效果如图:

GIF 2021-5-30 14-44-42.gif

接下来,我们看下页面加载性能分析图:

image.png

从图中可以清楚的看到脚本的0执行占用6s多,渲染用时将近1s,而且CPU占用从开始的46MB一直到196MB

那怎么解决这个问题呢?总体思路是这样的:让页面只显示我们能看到的东西,看不到的东西不显示,然后监听滚动条的变化,当滚动条变化的时候重新显示可见区域就完事了,简单画个图:

初始样子:

长列表优化.png

当滑动了一个位置:

长列表优化.png

我们只观察绿色边框区域就行了,当移动一个位置后,表示1的数据条消失了,表示7的数据条又出现了,其实只是位置发生了变化,这就是主要实现的思路。

代码实现:

APP.vue

//APP.vue
<template>
  <div id="app">
    <RecycleScroller 
      :items="items" 
      :itemSize="54" 
      class="scroller"
      v-slot="{item}"
      >

        <ListItem :item="item" />
    </RecycleScroller>
  </div>

</template>

<script>
import ListItem from "./
components/Listltem.vue";
import RecycleScroller from "
./components/RecycleScroller.vue";
var items = [];
for (var i = 0; i < 10000; i++) {
  items.push({
    id: i + 1,
    count: i + 1,
  });
}
export default {
  name: "
App",
  components: {
    RecycleScroller,
    ListItem,
  },
  data() {
    return {
      items,
    };
  },
};
</script>

<style>
#app {
  width: 100%;
  margin: 0 auto;
}
.scroller {
  width: 500px;
  height: 500px;
  margin: 0 auto;
}
</style>

ListItem.vue 组件

//ListItem.vue
<template>
  <div class="list-container">
    <span>id{{item.id}}</span>
    <span>name{{item.count}}</span>
    <span>age{{item.count}}</span>
  </div>

</template>

<script>
export default {
  props: {
    item:Object,
  },
};
</
script>

<style  >
  .list-container{
    margin0 auto;
    width500px;
    text-align: center;
    height54px;
    padding1em;
    display: grid;
    box-sizing: border-box;
    grid-template-columnsrepeat(3,1fr);
    box-shadow0 0 3px rgba(0000.5);
  }
</style>


RecycleScroller.vue 组件

// RecycleScroller.vue
<template>
  <div class="recycle-scroller-container" @scroll="setPool" ref="container">
    <div class="recycle-scroller-wrapper" :style="{ height: `${totalSize}px` }">
      <div
        class="recycle-scroller-item"
        v-for="poolItem in pool"
        :key="poolItem.item[keyField]"
        :style="{
          transform: `translateY(${poolItem.position}px)`,
        }"

      >

        <slot :item="poolItem.item"></slot>
      </div>
    </div>
  </div>

</template>

<script>
export default {
  props: {
    /
/数据的数组
    items: {
      type: Array,
      default: () => [],
    },
    /
/每一条数据的高度
    itemSize: {
      type: Number,
      default: 0,
    },
    keyField: {
      /
/给我的items数组中,每个对象哪个属性代表唯一且稳定的编号
      type: String,
      default: "id",
    },
  },
  data() {
    return {
      pool: [], /
/渲染池,保存当前需要渲染的数据
    };
  },
  mounted() {
    this.setPool();
  },
  computed: {
    totalSize() {
      return this.items.length * this.itemSize; /
/总高度,每一个元素的高度 * 数量
    },
  },
  methods: {
    /
/拿到需要被渲染的数据添加到 pool数组中
    setPool() {
      let scrollTop = this.$refs.container.scrollTop;
      let height = this.$refs.container.clientHeight;
      let startIndex = Math.floor(scrollTop /
 this.itemSize);//获取到要截取数据的起点
      let endIndex = Math.ceil((scrollTop + height) / this.itemSize);//获取到要截取数据的终点
      let scrollPos = startIndex * this.itemSize;
      this.pool = this.items.slice(startIndex, endIndex).map((it, i) => ({
        item: it,
        position: scrollPos + i * this.itemSize,
      }));
    },
  },
};
</script>

<style>
.recycle-scroller-container {
  overflow: auto;
}
.recycle-scroller-wrapper {
  position: relative;
}
.recycle-scroller-item {
  position: absolute;
  width: 100%;
  left: 0;
  top: 0;
}
</
style>

最终效果:

GIF 2021-5-31 19-53-44456.gif

同样是渲染10000条数据,我们来看下这种方案性能图:

image.png

从图中可以清楚的看到脚本的执行用了335ms,渲染用时6ms,内存占用14.3MB到30MB,跟第一次性能图可谓天差地别。

上面的代码大家没必要记,关于长列表优化这块是有一个插件的,名字叫vue-virtual-scroller,链接地址

接下来,我们在项目中使用这个插件:

  1. 安装  npm i vue-virtual-scroller

  2. 修改代码,将原先RecycleScroller移除,导入新安装的组件,别忘了还要引入css文件import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

APP.vue

//APP.vue
<template>
  <div id="app">
    <RecycleScroller 
      :items="items" 
      :itemSize="54" 
      class="scroller"
      v-slot="{item}"
      >

        <ListItem :item="item" />
    </RecycleScroller>
  </div>

</template>

<script>
import ListItem from "./
components/Listltem.vue";
import {RecycleScroller} from "
vue-virtual-scroller";
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';//记得还有引入css
// import RecycleScroller from "
./components/RecycleScroller.vue";
var items = [];
for (var i = 0; i < 10000; i++) {
  items.push({
    id: i + 1,
    count: i + 1,
  });
}
export default {
  name: "
App",
  components: {
    RecycleScroller,
    ListItem,
  },
  data() {
    return {
      items,
    };
  },
};
</script>

<style>
#app {
  width: 100%;
  margin: 0 auto;
}
.scroller {
  width: 500px;
  height: 500px;
  margin: 0 auto;
}
</style>

导入插件后,效果是一样的。

😊 好了, 以上就是我的分享,欢迎大家在评论区讨论鸭~

希望小伙伴们点赞 👍 支持一下哦~ 😘,我会更有动力的 🤞

浏览 43
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报