在 vue 渲染树结构是比较卡的,特别是数据大了,展开缩写都要几秒,那么解决问题的思路就是减少 dom 渲染,使用虚拟列表来模拟树结构,这个方式可以渲染大量的数据,缺点是每次点击展开、收缩都要重新更新列表,效果略差,但是支持渲染大数据量。
实现虚拟树的步骤:
测试中:渲染 5w 条数据平均在 200ms 以内
理论上流畅渲染的数量在10w 条左右,因为数据量太大的话 js 运算树节点都会很耗时
<vxe-list height="400" class="my-tree" :loading="loading" :data="list">
<template v-slot="{ items }">
<div
class="my-tree-item"
v-for="item in items"
:key="item.id"
:class="[`level-${item._LEVEL}`, {'has-child': item._HAS_CHILDREN, 'is-expand': item._EXPAND}]"
:style="{paddingLeft: `${item._LEVEL * 20}px`}">
<i class="tree-icon fa fa-chevron-right" @click="toggleTreeNode(item)">i>
<span class="tree-label">{{ item.label }}span>
div>
template>
vxe-list>
export default {
data () {
return {
loading: false,
list: []
}
},
created () {
this.loadTree(10)
},
methods: {
getTree (size) {
// 模拟后台数据
const result = []
let idKey = 0
for (let index = 0; index < size; index++) {
const item = {
id: ++idKey,
label: `节点 ${index}`
}
if (index) {
if (index % 33 === 0) {
const childList = []
for (let cIndex = 0; cIndex < 1000; cIndex++) {
childList.push({
id: ++idKey,
label: `子节点 ${index}-${cIndex}`,
children: [
{ label: `子节点 ${index}-${cIndex}-0` },
{ label: `子节点 ${index}-${cIndex}-1` },
{ label: `子节点 ${index}-${cIndex}-2` },
{ label: `子节点 ${index}-${cIndex}-3` },
{ label: `子节点 ${index}-${cIndex}-4` },
{ label: `子节点 ${index}-${cIndex}-5` },
{ label: `子节点 ${index}-${cIndex}-6` },
{ label: `子节点 ${index}-${cIndex}-7` }
]
})
}
item.children = childList
} else if (index % 22 === 0) {
const childList = []
for (let cIndex = 0; cIndex < 500; cIndex++) {
childList.push({
id: ++idKey,
label: `子节点 ${index}-${cIndex}`,
children: [
{ label: `子节点 ${index}-${cIndex}-0` },
{ label: `子节点 ${index}-${cIndex}-1` },
{ label: `子节点 ${index}-${cIndex}-2` }
]
})
}
item.children = childList
} else if (index % 9 === 0) {
const childList = []
for (let cIndex = 0; cIndex < 200; cIndex++) {
childList.push({
id: ++idKey,
label: `子节点 ${index}-${cIndex}`,
children: [
{ label: `子节点 ${index}-${cIndex}-0` },
{ label: `子节点 ${index}-${cIndex}-1` },
{ label: `子节点 ${index}-${cIndex}-2` },
{ label: `子节点 ${index}-${cIndex}-4` },
{ label: `子节点 ${index}-${cIndex}-5` }
]
})
}
item.children = childList
} else if (index % 6 === 0) {
const childList = []
for (let cIndex = 0; cIndex < 100; cIndex++) {
childList.push({
id: ++idKey,
label: `子节点 ${index}-${cIndex}`,
children: [
{ label: `子节点 ${index}-${cIndex}-0` },
{ label: `子节点 ${index}-${cIndex}-1` },
{ label: `子节点 ${index}-${cIndex}-2` },
{ label: `子节点 ${index}-${cIndex}-3` }
]
})
}
item.children = childList
} else if (index % 3 === 0) {
const childList = []
for (let cIndex = 0; cIndex < 10; cIndex++) {
childList.push({
id: ++idKey,
label: `子节点 ${index}-${cIndex}`,
children: [
{ label: `子节点 ${index}-${cIndex}-0` },
{ label: `子节点 ${index}-${cIndex}-1` }
]
})
}
item.children = childList
}
}
result.push(item)
}
return result
},
loadTree (size) {
this.loading = true
setTimeout(() => {
const trerData = this.getTree(size)
// 将树结构拍平,构建列表树结构
XEUtils.eachTree(trerData, (item, index, items, paths, parent, nodes) => {
// 层级
item._LEVEL = nodes.length - 1
// 是否展开
item._EXPAND = false
// 是否可视
item._VISIBLE = !item._LEVEL
// 是否有子节点
item._HAS_CHILDREN = item.children && item.children.length > 0
// 是否叶子节点
item._IS_LEAF = !item._HAS_CHILDREN
})
this.tree = trerData
this.refreshTree()
this.loading = false
const startTime = Date.now()
this.$nextTick(() => {
this.$XModal.message({ message: `渲染 ${this.fullList.length} 行,用时 ${Date.now() - startTime}毫秒`, status: 'info' })
})
}, 200)
},
// 切换树节点的展开、收缩
toggleTreeNode (row) {
if (row._HAS_CHILDREN) {
this.setTreeExpand(row, !row._EXPAND)
}
},
// 设置树节点的展开、收缩
setTreeExpand (row, isExpand) {
const matchObj = XEUtils.findTree(this.tree, item => item === row)
row._EXPAND = isExpand
if (matchObj) {
XEUtils.eachTree(matchObj.item.children, (item, index, items, path, parent) => {
item._VISIBLE = parent ? parent._EXPAND && parent._VISIBLE : isExpand
})
}
this.refreshTree()
},
// 展开、收缩所有树节点
allTreeExpand (isExpand) {
if (isExpand) {
XEUtils.eachTree(this.tree, item => {
item._EXPAND = item._HAS_CHILDREN
item._VISIBLE = true
})
} else {
XEUtils.eachTree(this.tree, item => {
item._EXPAND = false
item._VISIBLE = !item._LEVEL
})
}
this.refreshTree()
},
refreshTree () {
const treeList = XEUtils.toTreeArray(this.tree)
this.fullList = treeList
this.list = treeList.filter(item => item._VISIBLE)
}
}
}
在线运行 http://jsrun.net/3Z2Kp/edit