preface

  1. The LazyTree component is a complement to the Tree component in FES. Before we start, I would like to recommend our FES, an excellent management platform application solution, similar to vue-element-admin, but better to use. Interested friends can click on the portal to have a look, which really saves development time.
  2. The LazyTree component is currently included in the company’s internal version of fes2.0.
  3. LazyTree is named because the Tree component name is already used.

Gitee address

  1. Gitee Project link gitee.com/jiangxiZhan…
  2. LazyTree directory: gitee.com/jiangxiZhan…

The main file

Lazytree. vue entry root node, handle the main logic Lazytreenode. vue leaf node, handle selected eventsCopy the code

Encapsulates the background of the LazyTree component

Why should I encapsulate the LazyTree component

  1. I took over an old project of the company and found that the Tree structure was used in some places, but the Tree structure was not the Tree component in the company’s common component library, but a plug-in called liquor- Tree. The Tree component does not support the filter node function.
  2. There is a need for a tree structure to display the personnel information of the selected company. There are about 7000 people in the company, including the information of departments and departments, there are about 7500 pieces of data in total. When using liquor-tree to select the organizational structure of the whole company, it will be stuck, and the stuck time will be 2s to 3s, which is really unacceptable. After I took over, the business has always mentioned that they want to optimize this function.
  3. I tried to filter data by myself and passed it into the Tree component to realize the filtering function, but found that the effect was not satisfactory, and there was still the problem of lag in all selection.

Tree component and liquor- Tree data volume large card reason

  1. The Tree component renders the folded element directly, simply doing display: None. Rendering nearly ten thousand nodes without doing anything else is already too much. Once all nodes are selected, setChild event is recursively called, which is a heavy burden on memory and will lead to lag.
  2. When the selection is triggered, the liquor-tree component will do a recursion to trigger the checked event. In the recursion process, it will first find the node and then trigger the click event, which is costly. The following code
check() {
    if (this.node.checked()) {
      this.node.uncheck()
    } else {
      this.node.check()
    }
  }
Copy the code

Post a screenshot from Chrome Performance during the full selection and you can see that the cause of the stutter is indeed a checked call, which takes a whopping 65000msOf course, this recursive call problem also exists in the Tree component

Implementation process

How to write a tree component with good performance

  1. Lazy loading, when a node is not expanded we do not need to load its children, only when expanded.
  2. At present, the main reason for the lag of components is that the click events of each node are recursively called. The number of nodes requires the number of function calls, and the performance of function calls is not high. Well, THEN I guess we just need to circumvent the complexity of the recursive transfer. The reason for recursively calling checked is to update the selected state of each node. In fact, we only need to record all the selected and unselected states in data, and update the data in data every time we click one click.

Write a document and see what you need

Code implementation

Process data information passed in by the user
  1. The data information passed in by the user cannot be used directly. Some simple processing needs to be done. When the user passes in data or the defaultSelectedIds changes, the data needs to be processed and the processed data is set to list.
  2. The selected states are 0: unselected,1: half-selected, and 2: selected. If a child node has departments selected, the parent node will enter the half-selected state.
  3. The main code is shown below
lazyTree.vue transformData(list, level = 1, parentSelected = 0, Return list.map((item) => {const {id, name, expand = false, data = null, children = [] } = item; let { selected = 0 } = item; if (this.multiple === false && this.defaultSelectedIds.length === 0 && selected === true) { this.selectedId = id; } if (selected === true) { selected = 2; } selected = this.selectedIdsMap[id] ? 2 : selected; if (parentSelected === 2) { selected = 2; } const obj = { id, name, expand, data, level, isLeaf: children.length === 0, selected, isShow: true, parent } const newChildren = this.transformData(children, level + 1, selected, obj); obj.children = newChildren; return obj; }); }Copy the code
Update selection information
  1. The selected information here is mainly driven by data. The selected status of all non-leaf nodes is determined by their leaf nodes. In this way, when selecting a middle leaf node, we first update its selected state to 2, and then calculate the selected status of its parent node. The selected status of a parent node is determined only by its direct children.
  2. Update the child node main code
lazyTreeNode.vue

updateChildrenSelected(node, selected) {
    node.forEach((item) => {
        const { children } = item;
        item.selected = selected;
        this.updateChildrenSelected(children, selected);
    });
}
Copy the code
  1. Update the parent node to select the state main code
lazyTree.vue updateParentSelected(node) { if (! node || ! node.parent) { return; } let allSelectCount = 0; let someSelectCount = 0; const children = node.parent.children; children.forEach((item) => { const { selected } = item; if (selected === 1) { someSelectCount += 1; } else if (selected === 2) { allSelectCount += 1; }}); if (someSelectCount === 0 && allSelectCount === 0) { node.parent.selected = 0; } else if (someSelectCount > 0) { node.parent.selected = 1; } else if (allSelectCount > 0 && allSelectCount < children.length) { node.parent.selected = 1; } else if (allSelectCount === children.length) { node.parent.selected = 2; } this.updateParentSelected(node.parent); }Copy the code
Filter node function
  1. This allows the user to pass in the filterText field to filter the node. We only need to control the isShow field. Of course, anti – shake is also essential.
  2. The main code is as follows
Watch: {filterText: {immediate: true, handler(text) {clearTimeout(this.timer); this.timer = setTimeout(() => { this.filterLabel = text; this.filterTree(this.list, this.filterLabel.toLowerCase()); this.list.forEach((node) => { this.updateSelected(node); }); }, 300); FilterTree (list, filterLabel, show = false) {if (! list || list.length === 0) { return; } list.forEach((item) => { const { name, children = [] } = item; if (show === true || filterLabel.length === 0 || name.toLowerCase().indexOf(filterLabel) > -1) { item.isShow = true; this.filterTree(children, filterLabel, true); } else { item.isShow = this.filterTree(children, filterLabel, false); }}); return list.some(item => item.isShow) }Copy the code

The performance comparison

In the case of the same amount of data (7500 or so), all the tests were completed without any lag, and the completion time of many tests was controlled within 100ms. Please paste the screenshots in Performance

Analysis of reasons for good performance

For example, we pass in the following data structure:

Data2: [{name: jiangsu, id: '01, expand: true, children: [{name: "nanjing", id:' 0101 ',}, {name: suzhou, id: '0102', children: [{name: "wu jiang, id: '010201'}, {name: 'changshu, id:' 010202 '}]}}, {name: 'yunnan, id: '02' children: [...]}, {name: fujian, id: '03, children: [...]}].Copy the code

When we select Suzhou, we will first traverse its own child nodes, Wujiang and Changshu, add selected marks to their child nodes, and then directly update the selected state of the parent node. Once the update is complete, you can re-render the component. Let’s say we have n entries, and we have five levels in the tree, and the worst that can happen is that we’re going to pick all the root nodes, and then we’re going to have to iterate over every single entry and update the selected state, and the time is O(n), so it’s a very small computation. Instead of starting from the root node, finding each node and triggering its click event, we update the data directly in the root component, and then update the page. There is no other unnecessary operation, and the natural selection is smoother. Tested tens of thousands of data volume, is still very smooth, no one any stuck.