引言
性能优化一直都是吾辈努力的方向。React 官网其实已经介绍了如何去优化 React 项目(Optimizing Performance – React)。这次,我们看上了「虚拟化长列表」。
(感觉要写很久……
目前的分页有什么问题
目前项目里使用了「分页」来优化任务列表的渲染。其原理是,在初次渲染列表的时候,取前 50 条来渲染,然后当滚动条向下滚动到某个阈值的时候,触发渲染后 50 条,以此类推。
这个方案有什么问题呢?
首先,一次性渲染 50 条任务本身就是个很大的开销。当用户向下滚动到阈值的时候,会有比较长时间的无响应。
其次,当我们最终把所有任务都渲染出来了以后(假设总共有几百条任务),那么当前页面上的节点数量就会急速膨胀。我们知道,节点太多以后,浏览器去做布局、重绘、回流的时候会变慢很多。举个例子,当全部任务渲染出来以后,我们拖拽一条任务(原来的 jQueryUI 版本的拖拽),会发现它在任务列表上移动的时候严重掉帧。
这是最主要的 2 个问题。而「虚拟化长列表」,恰恰就能够一举解决它们。
什么是「虚拟化长列表」
简单来说就是只渲染你能看到的东西。其核心原理是,通过获取 scrollTop 和 滚动区域的 height,来确定需要渲染的范围是哪一段。scrollTop 告诉我们从哪个地方开始渲染,height 告诉我们需要渲染多少个元素。
原理其实很简单,不过我们还是想先看看有没有现成的开源方案可以用。但翻了一下市面上的UI库,比如 react-virtualized、react-tiny-virtual-list、rsuite、Fusion Design 等,基本没找到能同时满足 虚拟化 + 树状结构 + 可拖拽 的。没办法,只能自己写咯。
定义数据结构
因为我们有树状结构的需求,所以还是需要先确定一下数据结构。根据目前项目的情况来,在任务列表上总共是 3 层:SectionList(全部数据) -> Section(分组) -> Task(分组下的任务):1
2
3
4
5
6
7SectionList: [
Section: {
key: String,
list: Array,
collapse: Boolean
}
]
SectionList:最外层,由 Section 对象组成的数组
Section:用来存放分组相关数据的对象
key:作为分组的唯一标识
list: 分组下已排序的 Task 数组
collapse::业务相关状态,标识 Section 展开收起的状态
虚拟化设计
下面我们将一步步进行虚拟化的设计。先来做一点准备工作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import React from "react";
export default class List extends React.Compoennt {
constructor(props) {
super(props);
this.state = {
startIndex: [0, 0],
endIndex: [0, 0]
}
}
render() {
return (
<div className="list-scroller">
<div className="list">
{this.renderSections()}
</div>
</div>
)
}
renderSections() {}
}
我们创建了一个 List 组件,并在 state 上存放了 2 个状态:开始渲染和结束渲染的坐标。因为在我们的业务里只有 3 层结构,所以通过 2 个下标就能找到对应的 Task ,例如 SectionList[a].list[b]。