задний план
Проекту необходимо отобразить компонент дерева с более чем 5000 узлов, но при введении элементаКомпонент дереваПозже было обнаружено, что производительность была очень низкой, будь то прокрутка, развертывание/свертывание узлов или нажатие на узлы, это было очень очевидно.Использование производительности для запуска данных о производительности, чтобы найти проблему.
Идеи оптимизации
Из приведенного выше анализа видно, что проблемы с производительностью вызваны тем, что слишком много узлов для рендеринга, поэтому для решения этой проблемы необходимо минимизировать рендеринг узлов.Однако аналогичное решение в отраслиВиртуальный списокОсновная концепция виртуальных списков заключается в том, чтоСписки, управляющие визуализацией видимой области на основе прокруткиТаким образом, рендеринг узлов может быть значительно уменьшен, а производительность может быть повышена.
Конкретные шаги заключаются в следующем:
- «Плоские» данные дерева рекурсивной структуры, но сохраняют ссылки родительского и дочернего узлов (с одной стороны, это для удобства нахождения ссылок дочерних и родительских узлов, а с другой стороны, для удобство расчета списка данных видимой области)
- динамичныйРассчитать высоту области прокрутки (многие компоненты виртуальных длинных списков имеют фиксированную высоту, но поскольку это дерево, вам нужно свернуть/развернуть узлы, поэтому высота рассчитывается динамически)
- Отображает соответствующий узел на основе видимой высоты и расстояния прокрутки.
Код
минимальная реализация кода
<template>
<div class="b-tree" @scroll="handleScroll">
<div class="b-tree__phantom" :style="{ height: contentHeight }"></div>
<div
class="b-tree__content"
:style="{ transform: `translateY(${offset}px)` }"
>
<div
v-for="(item, index) in visibleData"
:key="item.id"
class="b-tree__list-view"
:style="{
paddingLeft: 18 * (item.level - 1) + 'px'
}"
>
<i :class="item.expand ? 'b-tree__expand' : 'b-tree__close' " v-if="item.children && item.children.length" />
<slot :item="item" :index="index"></slot>
</div>
</div>
</div>
</template>
<style>
.b-tree {
position: relative;
height: 500px;
overflow-y: scroll;
}
.b-tree__phantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.b-tree__content {
position: absolute;
left: 0;
right: 0;
top: 0;
min-height: 100px;
}
.b-tree__list-view{
display: flex;
align-items: center;
cursor: pointer;
}
.b-tree__content__item {
padding: 5px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
position: relative;
align-items: center;
cursor: pointer;
}
.b-tree__content__item:hover,
.b-tree__content__item__selected {
background-color: #d7d7d7;
}
.b-tree__content__item__icon {
position: absolute;
left: 0;
color: #c0c4cc;
z-index: 10;
}
.b-tree__close{
display:inline-block;
width:0;
height:0;
overflow:hidden;
font-size:0;
margin-right: 5px;
border-width:5px;
border-color:transparent transparent transparent #C0C4CC;
border-style:dashed dashed dashed solid
}
.b-tree__expand{
display:inline-block;
width:0;
height:0;
overflow:hidden;
font-size:0;
margin-right: 5px;
border-width:5px;
border-color:#C0C4CC transparent transparent transparent;
border-style:solid dashed dashed dashed
}
</style>
<script>
export default {
name: "bigTree",
props: {
tree: {
type: Array,
required: true,
default: []
},
defaultExpand: {
type: Boolean,
required: false,
default: false
},
option: {
// 配置对象
type: Object,
required: true,
default: {}
}
},
data() {
return {
offset: 0, // translateY偏移量
visibleData: []
};
},
computed: {
contentHeight() {
return (
(this.flattenTree || []).filter(item => item.visible).length *
this.option.itemHeight +
"px"
);
},
flattenTree() {
const flatten = function(
list,
childKey = "children",
level = 1,
parent = null,
defaultExpand = true
) {
let arr = [];
list.forEach(item => {
item.level = level;
if (item.expand === undefined) {
item.expand = defaultExpand;
}
if (item.visible === undefined) {
item.visible = true;
}
if (!parent.visible || !parent.expand) {
item.visible = false;
}
item.parent = parent;
arr.push(item);
if (item[childKey]) {
arr.push(
...flatten(
item[childKey],
childKey,
level + 1,
item,
defaultExpand
)
);
}
});
return arr;
};
return flatten(this.tree, "children", 1, {
level: 0,
visible: true,
expand: true,
children: this.tree
});
}
},
mounted() {
this.updateVisibleData();
},
methods: {
handleScroll(e) {
const scrollTop = e.target.scrollTop
this.updateVisibleData(scrollTop)
},
updateVisibleData(scrollTop = 0) {
const start = Math.floor(scrollTop / this.option.itemHeight);
const end = start + this.option.visibleCount;
const allVisibleData = (this.flattenTree || []).filter(
item => item.visible
);
this.visibleData = allVisibleData.slice(start, end);
this.offset = start * this.option.itemHeight;
}
}
};
</script>
Детали следующим образом:
- Относительное позиционирование всего контейнера используется, чтобы избежать прокрутки.переформатирование страницы
- Фантомный контейнер заставляет полосу прокрутки появляться, чтобы растянуть высоту
- Чтобы сгладить древовидные данные рекурсивной структуры, flattenTree добавляет атрибуты level, expand и visibel, которые представляют уровень узла, необходимость развертывания и видимость.
- contentHeight динамически вычисляет высоту контейнера, скрытые (свернутые) узлы не должны учитываться в общей высоте
Таким образом, древовидный компонент для рендеринга больших данных имеет базовый прототип.Далее давайте посмотрим, как разворачивать/сворачивать узлы.
Узел Развернуть Свернуть
Ссылка на дочерний элемент сохраняется в flattenTree, чтобы развернуть/свернуть, вам нужно только показать/скрыть дочерний элемент.
{
methods: {
//展开节点
expand(item) {
item.expand = true;
this.recursionVisible(item.children, true);
},
//折叠节点
collapse(item) {
item.expand = false;
this.recursionVisible(item.children, false);
},
//递归节点
recursionVisible(children, status) {
children.forEach(node => {
node.visible = status;
if (node.children) {
this.recursionVisible(node.children, status);
}
})
}
}
В заключение
Сравните некоторые данные о производительности до и после оптимизации
компонент дерева элементов
Первый рендер (все свернуто)
Оптимизированный компонент дерева
Первый рендер (полное расширение)
Расширение узла
компоненты большого дерева
окончательная упаковка вvue-big-treeКомпоненты для вызова, добро пожаловать в звезду~~~