задний план
Проекту необходимо отобразить компонент дерева с более чем 5000 узлов, но при введении элементаКомпонент дереваПозже было обнаружено, что производительность была очень низкой, будь то прокрутка, развертывание/свертывание узлов или нажатие на узлы, это было очень очевидно.Использование производительности для запуска данных о производительности, чтобы найти проблему.
Как видно из приведенного выше рисунка, за исключением Idle, общее затраченное время составляет12s, где Сценарии взяли10s Как видно из приведенного выше рисунка, во время Scripting, кроме Observe, чаще всего вызываетсяcreateChildrenсоздать экземпляр vueИдеи оптимизации
Из приведенного выше анализа видно, что проблемы с производительностью вызваны тем, что слишком много узлов для рендеринга, поэтому для решения этой проблемы необходимо минимизировать рендеринг узлов.Однако аналогичное решение в отраслиВиртуальный списокОсновная концепция виртуальных списков заключается в том, чтоСписки, управляющие визуализацией видимой области на основе прокруткиТаким образом, рендеринг узлов может быть значительно уменьшен, а производительность может быть повышена.
Конкретные шаги заключаются в следующем:
- «Плоские» данные дерева рекурсивной структуры, но сохраняют ссылки родительского и дочернего узлов (с одной стороны, это для удобства нахождения ссылок дочерних и родительских узлов, а с другой стороны, для удобство расчета списка данных видимой области)
- динамичныйРассчитать высоту области прокрутки (многие компоненты виртуальных длинных списков имеют фиксированную высоту, но поскольку это дерево, вам нужно свернуть/развернуть узлы, поэтому высота рассчитывается динамически)
- Отображает соответствующий узел на основе видимой высоты и расстояния прокрутки.
Код
минимальная реализация кода
<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);
}
})
}
}
В заключение
Сравните некоторые данные о производительности до и после оптимизации
компонент дерева элементов
Первый рендер (все свернуто)
scripting: 11525ms rendering:2041 мс Примечание: полностью расширенный прямо застрял scripting: 84ms rendering: 683msОптимизированный компонент дерева
Первый рендер (полное расширение)
scripting:Повышение производительности на 1671 мс по сравнению с до оптимизации6,8 раза rendering:Повышение производительности на 31 мс по сравнению с до оптимизации65 разРасширение узла
scripting:86 мс Производительность стабильна до оптимизацииrendering:Повышение производительности на 6 мс по сравнению с до оптимизации113 разкомпоненты большого дерева
окончательная упаковка вvue-big-treeКомпоненты для вызова, добро пожаловать в звезду~~~