The effect
Only realized the basic function, record learning, defects are not perfect
The problem
- Rendering freezes when there is too much data. Because there are so many DOM’s.
Implementation scheme
-
- Flatten the tree data and DOM
-
- The virtual long list controls the number of DOM renders
1. The flat
It can be seen from the figure above that the parent node DOM after flattening is the same as the parent node DOM. Next, we will realize the flattening of tree data.
const treeData = [
{
id: 1.title: "Class 1".children: [{id: 4.title: "Course 1-1" },
{
id: 5.title: "Course 1-2".children: [{id: 6.title: "Curriculum 1-2-1" },
{ id: 7.title: "Curriculum 1-2-2"},],},],}, {id: 2.title: "Class 2" },
{ id: 3.title: "Three courses"},];const flatData = [
{ id: 1.title: "Class 1" },
{ id: 4.title: "Course 1-1" },
{ id: 5.title: "Course 1-2" },
{ id: 6.title: "Curriculum 1-2-1" },
{ id: 7.title: "Curriculum 1-2-2" },
{ id: 2.title: "Class 2" },
{ id: 3.title: "Three courses"},]Copy the code
Flattening the data by converting the data format of treeData to faltData
Recursion and iteration achieve flattening
Let’s talk about the similarities and differences.
-
Recursion uses a selection structure that makes the structure of the program clearer, more concise, and easier to understand, thus reducing the time to read the code. But a lot of recursive calls create copies of the function, which can consume a lot of time and memory.
-
Iteration uses a circular structure that does not require repeated function calls and takes up extra memory. Therefore, we should choose different code implementation methods depending on the situation. In the algorithm we will encounter a lot of recursive implementation cases, all recursion can be converted into non-recursive implementation, the essence of transformation is: recursive is the parser (engine) to help us do the stack access, non-recursive is to manually create the stack to simulate the stack access process
Let’s do this through iteration
function faltten(tree) {
let flatData = []
let stack = [...tree]
while (stack.length) {
let node = stack.shift()
if(node.children) { stack.unshift(... node.children) } flatData.push({id: node.id, title: node.title })
}
return flatData
}
Copy the code
Now let’s do it recursively
function faltten(tree) {
let flatData = []
for (var i = 0; i < data.length; i++) {
formatData.push({
id: data[i].id,
title: data[i].title,
})
if (data[i].children) {
formatData = formatData.concat(faltten(data[i].children))
}
}
return formatData
}
Copy the code
To implement the flattened tree component function, our data fields need expand, Visible, children, and Level fields. General structure:
{key: title: level: identifies the node level through level, and distinguishes the node levels through the CSS. Visible: shows and hides the attributes of the node. Expand: expands the state. Store references to child nodes, and control the display and hiding of child nodes by reference
Through the above analysis, we continue to implement the flattening process after the new fields
Iterative implementation method
flatten () {
let flatData = []
let stack = [...tree]
let parentIndex = {} // Store the level index
while (stack.length) {
let node = stack.shift()
if(! node.level) { node.level =0
node.visible = true
}
if (node.children) {
parentIndex[node.level] = flatData.length // The index of node is equal to the length of flatData, since node is pushed next
const children = node.children.map(item= > { // Set the subclass level
return{... item,level: node.level + 1.visible: node.expand}} }) stack.unshift(... children) } flatData.push({... node,children: []})
if(node.level ! = =0) { // Add a subclass reference (node must have a parent node as long as level is not the first level)
flatData[parentIndex[node.level - 1]].children.push(flatData[flatData.length - 1]) // Add itself to the children property of the current node's parent}}return flatData
}
Copy the code
Recursive implementation method
flatten (data, arr = [], parent = null, level = 0, visible = true, children = [], insert = null) { arr.push({... data, level, parent, visible, children})if(insert ! = =null) {
arr[insert].children.push(arr[arr.length - 1]) // Add a child reference to the children field of the parent node
}
if (data.children) {
insert = arr.length - 1 // The parent node inserts the child node reference index
data.children.forEach((item) = > {
this.flatten(item, arr, arr[arr.length - 1], level + 1, data.expand, [], insert)
})
}
return arr
}
Copy the code
The difficulty lies in storing the data reference of the child node. Storing the reference of the child node is because the child node object and children object point to the same memory address. Therefore, when hiding the node later, we can directly obtain the children field of the current clicked node. Hide the Visible property.
Flattening tree rendering of data
1. Traversal of tree nodes
<div
:key="i"
class="tree-item"
v-show="item.visible"
@click="handleExpand(item)"
v-for="(item, i) in unHiddenList.slice(startIndex, endIndex)"
:style="`transform:translateX(${item.level * 20}px)`"
>
Copy the code
The hierarchy of nodes is controlled by the level attribute
UnHiddenList represents a list of data whose visible attribute is false (that is, not expanded)
2. Implementation of node expansion and hiding
handleExpand (node) { // Click the node operation
conststatus = ! node.expand node.expand = statusif (status && node.children.length > 0) {
node.children.forEach((item) = > { // The child of the node will be clicked to display
item.visible = true})}else if(! status) {this.handleClose(node.children) // Hide the descendant node of the click node
}
},
handleClose (node) { // Hide the node
node.forEach((item) = > {
item.visible = false
if (item.children.length > 0) {
item.expand = false
this.handleClose(item.children)
}
})
}
Copy the code
Virtual rolling
Dom structure
<div class="wrap" ref="wrap" @scroll="handleScroll">
<div class="invent-space" :style="`height:${contentHeight}`"></div><! -- Virtual scroll height -->
<div class="container" ref="list">
<! -- Node traversal -->
</div>
</div>
Copy the code
ContentHeight represents the entire scrolling contentHeight contentHeight = unhiddenlist.length * itemHeight
Listening for scroll events
updateVisibleData (scrollTop = 0) {
requestAnimationFrame(() = > {
if (scrollTop < (this.unHiddenList.length - 20) * this.itemHeight) { // Less than scrollable distance (unHiddenList unhidden node list)
this.$refs.list.style.transform = 'translate3d(0,' + scrollTop + 'px,0)' // Synchronize scrolling
this.startIndex = Math.floor(scrollTop / this.itemHeight) // Number of nodes that have been rolled
this.endIndex = this.startIndex + 20 // The number of scrolls plus the number of viewable areas}})}Copy the code
Where 20 represents visual item, 25 represents the height of item, and the number can be set according to requirements. The reason why transform is used to realize virtual scrolling is that transform is located on the Composite Layers, while width, left and margin are located on the Layout layer. Changes to the Layout layer must result in Paint Setup and Paint -> Composite Layers, so transform animations are smoother than left ones.
Refer to this article