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:

  1. 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)
  2. 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)
  3. 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:

  1. The entire container uses relative positioning to avoid page backflow during scrolling

  2. The phantom container makes the scroll bar appear in order to spread the height

  3. FlattenTree adds level, expand, and Visibel attributes to flat tree data of recursive structure, representing node level, expansion, and visibility respectively

  4. 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