This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Recently, I live with the village head teacher to share the construction of vue Devui open source component library. The first two phases start from 0 to develop a tree component for 🌰, introducing how to achieve a tree component that can render multi-layer nodes.

Implement a tree component that can render a layer of nodes

Implement a tree component that can render multiple nodes with an expanded icon

The final result is as follows:

This just implements the rendering logic, the minus sign icon in front of the tree node is not clickable, the node is not collapsible.

This time we will take you to realize the function of clicking the icon to expand/collapse the tree node.

The final result

The final effect we need to achieve is as follows:

Add the Open flag

The general structure of the data passed to the tree component looks like this:

[{label: 'level 1, level: 1, the children: [{label:' secondary 1-1, level: 2,}]},...Copy the code

So I don’t know which nodes need to be expanded and which need to be collapsed, so the first step is to add an open field to the nodes that need to be expanded.

For example, we want the following nodes to expand and everything else to collapse:

  • Level 2
  • Level 3
  • The secondary 3 to 2

The reconstructed data structure is as follows:

[{label: 'level1 ', level: 1, children: [...]}, {label:' level2 ', level: 1, open: true, // Add children: [...]}, {label: 'level3 ', level: 1, open: true, // add children: [{label:' level3 ', level: 2, open: 1, children: 1, open: 2, children: 2, open: 2, children: 2, children: 2, open: 2, children: 3, children: 2, open: 2, children: 3, True, / / new children: [...]}}, {label: 'level 4, level: 1,}]Copy the code

Render expand/collapse ICONS

In the absence of the open field, all nodes are expanded by default, and all the ICONS in front of the node are minus signs to indicate expansion.

Now that we have the open field, we can render an icon that expands (minus) or collapses (plus), so we need to modify the renderNode method.

const renderNode = (item) => {
  return (
    <div
      class="devui-tree-node"
      style={{ paddingLeft: `${24 * (item.level - 1)}px` }}
    >
      {
        item.children
          
          // Before
          // ? <IconOpen class="mr-xs" />
          
          // After
          ? item.open
            ? <IconOpen class="mr-xs" />
            : <IconClose class="mr-xs" />
          
          : <Indent />
      }
      { item.label }
    </div>
  )
}
Copy the code

Basic render logic

  1. If the current node has no child nodes, render directly, the node has no icon, and the corresponding number of Indent placeholders are displayed according to the current hierarchy
  2. If the current node has child nodes, the open property is not true, and the immediate rendering (without rendering child nodes) is preceded by IconClose
  3. If the current node has children and the open property is true, then render the current node + its first-level children, preceded by IconOpen
  4. If the child node also contains a node whose open is true, the same can be done

Only nodes that are expanded are rendered

To render the specified nodes, we perform some transformations to the previous nested data structure:

  • Pat the data flat
  • Filter node data whose open is true

The basic idea of transformation is as follows:

  • Recursively through the reduce method, the initial value is an empty array[]
  • It then checks whether the item data has an open attribute
  • If so, concatenate the data and its children
  • If not, the data is simply spliced
Const openedTree = (tree) => {return tree.reduce((acc, item) => (item.open? acc.concat(item, openedTree(item.children)) : acc.concat(item) ), []) } const openedData = openedTree(data)Copy the code

At this point the effect is already there, but it’s not interactive yet.

Bind click events to nodes

To expand/collapse a node by clicking on an icon, you need to bind a click event to the node icon.

const renderNode = (item) => { return ( <div class="devui-tree-node" style={{ paddingLeft: `${24 * (item.level - 1)}px` }} > { item.children ? item.open ? <IconOpen class="mr-xs" onClick={() => toggle(item)} /> / <IconClose class=" MR-xs "onClick={() => toggle(item)} <Indent />} {item.label} </div>)}Copy the code
Const toggle = (item) => {// expand/fold logic}Copy the code

Handles expansion/collapse logic

Expand/collapse, which essentially changes the open field of the current node data:

  • If the open field is currently true, the node is expanded and should be set to false when the icon is clicked
  • If there is no open field or the open field is false, the node is folded up and should be set to true when the icon is clicked
const toggle = (item) => { item.open = ! Item. open // Change the open field of the current node}Copy the code

This completes our goal:

Implement a tree that can expand/collapse

However, the current code is written in the Tree component setup method, including renderNode and other methods before, the setup method has 60+ lines of code. If other functions continue to be added, the setup code will become more and more unreadable and difficult to maintain, and the more bugs will occur.

So it needs to be refactored, using Vue3’s Composition API, to extract the variables and logic associated with node expansion/collapse into a separate use-toggle.ts file.

composables/use-toggle.ts

import { ref } from 'vue' export default function useToggle(data: unknown): any { const openedTree = (tree) => { return tree.reduce((acc, item) => ( item.open ? acc.concat(item, openedTree(item.children)) : acc.concat(item) ), [])} const openedData = ref(openedTree(data)) // Const toggle = (item) => {console.log('toggle', item, item.id, item.open); item.open = ! item.open openedData.value = openedTree(data) } return { openedData, toggle, } }Copy the code

In tree.tsx, you just need to introduce the required variables and methods.

Import useToggle from './composables/use-toggle' setup(props) {const {openedData, Toggle} = useToggle(data.value)Copy the code

summary

This article focuses on adding expansion/collapse functionality to a Tree component step by step and using VUe3’s composite API to remove this functionality from Setup.