13-Vue长列表优化 vue-virtual-scroller

回答:

在大型的企业级项目中经常要渲染大量的数据,这种长列表是一个很普遍的场景,当列表内容越来越多就会导致页面滑动卡顿白屏数据渲染较慢的问题;

这种情况主要发生在小程序、移动端或者后台管理的页面当中;

通常我们会采用分页的方式进行内容的逐渐获取,但是当内容越来越多时;

比如移动端的下拉刷新,不停的往上翻,到底部会加载更多内容,这样一来列表中会新增很多的元素,元素多了以后会触发浏览器的重排重绘,无论是内存的占用还是GPU的渲染都会带来很大的性能损耗,导致页面滑动卡顿数据渲染较慢的问题;

处理时要根据情况做不同处理:

  • 避免大数据量:采取分页的方式获取
  • 避免渲染大量数据:vue-virtual-scroller 插件等虚拟列表方案,只渲染视口范围内数据
  • 避免更新:使用 v-once 方式只渲染一次
  • 优化更新:通过 v-memo 缓存子树,有条件更新,提高复用,避免不必要更新
  • 按需加载数据:采用懒加载的方式,比如 tree 组件子树的懒加载

总之,还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚拟列表的方式优化渲染数量;最后优化更新,如果不需要更新可以 v-once 避免更新,需要更新可以 v-memo 进一步优化大数据更新性能,其他可以采用的是交互方式优化,无限滚动,懒加载方案等。

页面卡顿的原因

根源:大量DOM元素的 reflow 和 repaint

修改是对当前DOM元素的修改,更新却是所有的DOM都更新

优化思路:

1、懒渲染
  • 懒加载,常见的长列表优化方案,常见于移动端
  • 原理:每次只渲染一部分,等渲染的数据即将滚动完时,再渲染下面部分
  • 优点:每次渲染一部分数据,速度快
  • 缺点:数据量大时,页面中依然存在大量DOM节点,占用内存过多,降低浏览器渲染性能,导致页面卡顿
  • 使用场景:数据量不大的情况下(比如1000条,具体还要看每条数据的复杂程度)
2、分页渲染

一般是后端给我们数据,我们只需把页码数,每页展示的数据量,给后端,后端给我们数据我们进行展示即可。

3、可视区域渲染

原理: 只渲染页面可视区域的列表项,非可视区域的数据 “完全不渲染”(预加载前面几项和后面几项) ,在滚动列表时动态更新列表项,为了防止白屏,所以实际会多加载几条数据

自己实现可视区渲染

父组件中

  • 使用 computed 缓存模拟一万条数据,
  • 通过属性绑定将 item (数据)、 size (每条数据的高度)和  showNumber (每次渲染的数据条数)传递给子组件

子组件中

  • 通过 props 接收父组件传递过来的属性;
  • 最外层盒子设置 overflow-y: scroll; 实现纵向滚动;
  • 最外层盒子固定高度 (最外层盒子高度 = size * showNumber);
  • 给外层盒子监听滚动事件,计算被卷起的数据高度;
  • 计算可视区数据的起始索引,start = 卷起的高度 /  单条数据高度;
  • 计算可视区数据的结束索引,end = 起始索引 + 可视区可展示的条数;
  • 取可视区域起始索引 start 和结束索引 end 的数据展示到可视区域;

父组件

模拟一万条数据

创建了一个拥有10000个元素的空数组,利用 fill() 函数初始化,所有的值都为空,再利用 map, 给数组返回 id 和 content,也就实现了数组赋值;

computed: {
	item () {
		return Array(10000).fill('').map((item, index) => ({
			id: index,
			content: `列表内容`+ index
		}))
	}
}

 子组件

ui结构

list 是可视区域

bar 是撑开盒子,使其能纵向滚动

  • 可视区容器:可以看作是在最底层,容纳所有元素的一个盒子。
  • 可滚动区域:可以看作是中间层,假设有 10000 条数据,每个列表项的高度是 50,那么可滚动的区域的高度就是 10000 * 50。这一层的元素是不可见的,目的是产生和真实列表一模一样的滚动条。
  • 可视区列表:可以看作是在最上层,展示当前处理后的数据,高度和可视区容器相同。可视区列表的位置是动态变化的,为了使其始终出现在可视区域。

理解以上概念之后,我们再看看当滚动条滚动时,我们需要做什么:

  1. 根据滚动距离和 item 高度,计算出当前需要展示的列表的 startIndex
  2. 根据 startIndex 和 可视区高度,计算出当前需要展示的列表的 endIndex
  3. 根据 startIndex 和 endIndex 截取相应的列表数据,赋值给可视区列表,并渲染在页面上
  4. 根据滚动距离和 item 高度,计算出可视区列表的偏移距离 startOffset,并设置在列表上

 

 接收父组件传递过来的数据  ,定义初始下标和结束下标

计算容器高度和被卷起的数据高度

给container监听滚动事件,起始下标就是卷去的数据条数,向下取整

结束下标就是 起始下标 + 要展示的数据条数

使用虚拟列表 vue-virtual-scroller 实现

对于长列表来说,我们大部分的操作都是:

1.懒加载,分页,

2.Object.freeze冻结数组取消响应式,因为大多时候都是展示

3.高清图替换成缩略图,因为很多时候长列表的图尺寸都比较小,所以可以用小图来代替

以上能解决大部分的长列表问题,在可以分页的情况下

当不能分页时,采用可视区渲染

items高度固定(RecycleScroller)

下载插件

yarn add vue-virtual-scroller --save

 在 main.js 注册

import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
import VueVirtualScroller from "vue-virtual-scroller";

Vue.use(VueVirtualScroller);

 以下是必填项

Props

解释

默认值

items
可视区域要渲染的数据列表 --
item-size
每项数据的高度 null
style
可视区域的外层盒子高度 --
v-slot = { item }
插槽,拿到每项数据item --

 以下按需要修改

Props

解释

默认值

prerender
告诉服务端(SSR)每次渲染几条数据 0
buffer
可视区域外多渲染的数据高度,避免滚动留白 200
keyField
用于标识项目和优化管理渲染视图的字段 id

固定高度的作用域插槽参数

Slot

解释

默认值

item
每项数据 --
index
每项数据的下标 --
active
视图是否处于活动状态。活动视图被视为可见视图,并由RecycleScroller定位。非活动视图不被视为可见,并且对用户隐藏。如果视图处于非活动状态,则应跳过任何与渲染相关的计算。 --

items高度固定(RecycleScroller),可上拉加载

Events

解释

默认值

resize
重新计算大小时触发 --
visible
当滚动条认为自己在页面中可见时触发 --
hidden
当滚动条隐藏在页面中时触发 --
update (startIndex, endIndex)
每次更新视图时发出,仅当 :emitUpdate=“true” false

实现思路:

  • 添加 :emitUpdate="true" 和 @update 事件;
  • update函数传入两个形参,start 和 end ,判断当 end 等于数组列表的长度时,去请求接口来获取新的数据,然后加入到数组当中。

 items高度不固定(DynamicScroller),可上拉加载

Props(参数)

解释

默认值

item(必填)
每项数据 --
active(必填)
保持视图,数据处于 active 状态,将防止不必要的大小重新计算。 --
sizeDependencies
影响高度的值,如果发生变化,则重新计算 --
watchData
深入监视更改以重新计算大小(不推荐,可能会影响性能) false
tag
组件要呈现的元素 div
emitResize
每次重新计算大小时发出事件(可能会影响性能) false
minItemSize
列表项初次渲染使用的最小高度

Events(事件)

解释

默认值

resize
重新计算大小时触发,仅当 :emitUpdate=“true” false

小程序长列表优化实践 - 知乎

猜你喜欢

转载自blog.csdn.net/iaz999/article/details/131527796