The effect

Only realized the basic function, record learning, defects are not perfect

The problem

  1. Rendering freezes when there is too much data. Because there are so many DOM’s.

Implementation scheme

    1. Flatten the tree data and DOM
    1. 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 = [
  {
    id1.title"Class 1".children: [{id4.title"Course 1-1" },
      {
        id5.title"Course 1-2".children: [{id6.title"Curriculum 1-2-1" },
          { id7.title"Curriculum 1-2-2"},],},],}, {id2.title"Class 2" },
  { id3.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