Author: jayzou
Segmentfault.com/a/119000002…
background
The project needed to render a Tree component of 5000+ nodes, but after the introduction of element Tree component, the performance was found to be very poor, whether scrolling, expanding/unwinding nodes or clicking nodes were very obvious, and the problem was found by running performance data
As you can see from the figure above, the total elapsed time, excluding Idle, is 12s, of which Scripting takes 10s
As you can see from the figure above, most of the Scripting time, except for Observe, is spent calling createChildren to create vUE instances
Optimization idea
Can be seen from the above analysis of performance problems because rendering nodes lead to too much, so to solve this problem is to minimize the render of the node, however, in the industry and the similar solution is the core concept of the virtual virtual list according to scroll to control rendering a list of the visible area In this way, You can dramatically reduce node rendering and improve performance
The specific steps are as follows:
- Flatten the recursive tree data, but keep references to parent and child (on the one hand, to find references to child and parent nodes, and on the other hand, to calculate the list of visible areas)
- Dynamically calculate the height of the scroll area (many components of the virtual long list are fixed height, but because this is a tree, the node needs to be collapsed/expanded, so the height is dynamically calculated)
- Render the corresponding nodes according to the height visible and how far they are rolled
Code implementation
Minimal code implementation
<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: {// Configure Object type: Object, required: true, default: {}}}, data() {return {offset: 0, // translateY offset 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>Copy the code
Here are the details:
-
The entire container uses relative positioning to avoid page backflow during scrolling
-
The phantom container makes the scroll bar appear in order to spread the height
-
FlattenTree adds level, expand, and Visibel attributes to flat tree data of recursive structure, representing node level, expansion, and visibility respectively
-
ContentHeight dynamically calculates the height of containers; hidden (stowed) nodes should not be counted as part of the total height
This gives you a basic prototype of the tree component for rendering big data. How does node expansion/collapse work
Node expansion and collapse
References to children are kept in flattenTree, and only need to be shown/hidden by expanding/collapsing them
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); }}}})Copy the code
conclusion
Compare some performance data before and after optimization
Element tree component
First render (all folded up)
scripting: 11525ms
rendering: 2041ms
Note: All expansion directly stuck
scripting: 84ms
rendering: 683ms
Optimized tree component
First render (full unfold)
Scripting: 1671ms improved performance by 6.8 times compared to pre-optimization
Compared with before optimization, the performance is improved 65 times
Node expansion
Scripting: Consistent performance before 86MS optimization
Compared with before optimization, the performance is improved 113 times
The big tree components
Finally encapsulated into vuE-big-tree component for call, welcome star~~
More exciting please pay attention to the public number
Java geek mind
Scan wechat and follow the official account