今天给大家分享是一个 Tree 组件的虚拟列表,和往常大家经常使用纯一维数据进行的虚拟列表不太一样,但是总体思路大致相同。
背景
公司要做一个展示组织架构信息表,大公司可能有上万个人员,因为存在部门和子部门,是一个树形结构,使用传统的递归写的 Tree 组件加载,页面会直接崩溃。这个时候就需要一个虚拟 Tree 组件的诞生了。
方案
分析现状
我们先回忆一下传统的虚拟列表的方案。
由图片我们可以看到。虚拟列表是通过滚动列表,动态的显示数组中下标从 i 到 j 的数据,以达到部分渲染,减少 dom 数量,从而防止浏览器全量渲染导致的卡顿和崩溃的问题。
那我们能不能让 Tree 组件直接套用这个思路了,当然是可以的。但是如果这个 Tree 组件的层级很深了,每个层级也有上千条数据了,按照之前的思路是不是就不行了。因为在展开节点的时候,子节点数据量很大的话,那会直接全量渲染,导致页面卡顿和崩溃,有人会说我在展开数据后,重新计算该渲染哪些数据了,那不就行了,道理是这样的,那我们下面就介绍该怎么计算获得新的展示数据。
json
// 这种大量一维数据结构当然可以
[
{
lable: 'xxx',
value: 1,
}
// ...
]
// 如果是这种深度很深的结构,还是找个老思路,可能就没办法了。
[
{
lable: 'xxx',
value: 1,
children: [
{
label: 'xxxx-1',
value: '1-1',
children: [
{
label: 'xxxx-2',
value: '2-1',
}
]
}
// ...
]
}
]
解决思路
大致分析
- 既然虚拟列表是针对一维的数据结构,那我们是不是可以将 Tree 数据结构拍平成一维的,答案肯定是可以的。
- 数据既然是一维的,那是不是可以直接复用传统的虚拟列表方案了。
细化细节
有人会问,Tree 动态拍平成数组,那怎么合理的展示 Tree 的结构了,其实我们可以用 padding-left 来模拟展示树形结构。
- 首次加载组件,渲染 Tree 数据结构的外层一维数据结构。
- 点击展开子节点时候,动态的将子节点下所有数据,拿出来,拼接在父节点下标 index + 1 上。
- 获得新的一维数据,重新计算渲染展示的结束下标。
- 周而复始的动态拍平数据,以此维护一个一维数据,虚拟 Tree 组件好像就写完了。
js
const nodes = [
{
label: 'xxx',
value: '1',
children: [
{
label: 'xxx-1',
value: '1-1',
},
]
},
]
// children 节点的渲染取消传统的递归组件渲染的形式,采用动态的维护 拍平数据 去做展开和收缩
const flatten = () => {
const expandedKeys = ['1']
const flattenNodes = []
function traverse() {
const stack = []
for (let i = nodes.length - 1; i >= 0; --i) {
stack.push(nodes[i])
}
while (stack.length) {
const node = stack.pop()
if (!node) continue
flattenNodes.push(node)
// 只渲染展示的节点
if (expandedKeys.has(node.key)) {
const children = node.children
if (children) {
const length = children.length
for (let i = length - 1; i >= 0; --i) {
stack.push(children[i])
}
}
}
}
}
traverse()
return flattenNodes
}
参考资料
Element Plus: github.com/element-plu...