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{
margin: 0 auto;
width: 500px;
text-align: center;
height: 54px;
padding: 1em;
display: grid;
box-sizing: border-box;
grid-template-columns: repeat(3,1fr);
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
</style>
效果如图:
接下来,我们看下页面加载性能分析图:
从图中可以清楚的看到脚本的0执行占用6s
多,渲染用时将近1s
,而且CPU占用从开始的46MB
一直到196MB
那怎么解决这个问题呢?总体思路是这样的:让页面只显示我们能看到的东西,看不到的东西不显示,然后监听滚动条的变化,当滚动条变化的时候重新显示可见区域就完事了,简单画个图:
初始样子:
当滑动了一个位置:
我们只观察绿色边框区域就行了,当移动一个位置后,表示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{
margin: 0 auto;
width: 500px;
text-align: center;
height: 54px;
padding: 1em;
display: grid;
box-sizing: border-box;
grid-template-columns: repeat(3,1fr);
box-shadow: 0 0 3px rgba(0, 0, 0, 0.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>
最终效果:
同样是渲染10000条数据,我们来看下这种方案性能图:
从图中可以清楚的看到脚本的执行用了335ms
,渲染用时6ms
,内存占用14.3MB到30MB,跟第一次性能图可谓天差地别。
上面的代码大家没必要记,关于长列表优化这块是有一个插件的,名字叫vue-virtual-scroller
,链接地址
接下来,我们在项目中使用这个插件:
安装
npm i vue-virtual-scroller
修改代码,将原先
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>
导入插件后,效果是一样的。
😊 好了, 以上就是我的分享,欢迎大家在评论区讨论鸭~
希望小伙伴们点赞 👍 支持一下哦~ 😘,我会更有动力的 🤞