Vue: 如何实现树状收展及勾选联动

119 阅读3分钟
  1. 设计数据结构:我们首先需要设计一个合适的数据结构来存储树形数据。假设我们的树形数据结构如下:
[
  {
    id: 1,
    label: '节点1',
    children: [
      {
        id: 2,
        label: '节点1-1',
        children: [
          {
            id: 3,
            label: '节点1-1-1',
            children: []
          },
          {
            id: 4,
            label: '节点1-1-2',
            children: []
          }
        ]
      },
      {
        id: 5,
        label: '节点1-2',
        children: []
      }
    ]
  },
  {
    id: 6,
    label: '节点2',
    children: []
  }
]
  1. 构建组件层级结构:在Vue中,我们可以使用递归组件来构建树状结构。创建一个名为TreeNode的组件,并在模板中递归调用自身来渲染子节点。
<template>
  <div>
    <div @click="toggle">
      {{ node.label }}
      <input type="checkbox" v-model="isChecked" />
    </div>
    <div v-show="isExpanded">
      <tree-node v-for="child in node.children" :node="child" :key="child.id"></tree-node>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TreeNode',
  props: ['node'],
  data() {
    return {
      isExpanded: false,
      isChecked: false
    }
  },
  methods: {
    toggle() {
      this.isExpanded = !this.isExpanded;
    }
  }
}
</script>
  1. 实现展开和收起功能:在TreeNode组件中,我们为展开/收起按钮绑定了点击事件toggle()。通过修改isExpanded属性的值,我们可以控制该节点的子节点是否显示。

  2. 实现勾选联动功能:在TreeNode组件中,我们为勾选框绑定了v-model指令,将其与isChecked属性进行双向绑定。然后,我们使用计算属性来实现勾选状态的联动效果。

computed: {
  isIndeterminate() {
    if (this.node.children.length === 0) {
      return false;
    }
    const checkedCount = this.node.children.filter(child => child.isChecked).length;
    return checkedCount > 0 && checkedCount < this.node.children.length;
  },
  checkAll() {
    if (!this.isExpanded) {
      return false;
    }
    return this.node.children.every(child => child.isChecked);
  }
},
watch: {
  isChecked(newValue) {
    this.$emit('update:checked', newValue);
    if (this.node.children.length > 0) {
      this.node.children.forEach(child => child.isChecked = newValue);
    }
  }
}
  1. 更新UI界面:在TreeNode组件的模板中,我们根据节点的展开和勾选状态来动态显示相应的内容和样式。例如,通过v-show指令控制子节点的显示与隐藏,通过绑定isChecked属性来实现勾选框的状态更新。

  2. 处理用户交互事件:在父组件中使用<tree-node>标签来渲染根节点,并监听节点的展开/收起和勾选事件。我们可以通过props和$emit实现组件之间的通信。

<template>
  <div>
    <tree-node v-for="node in treeData" :node="node" :key="node.id" @update:checked="handleCheck"></tree-node>
  </div>
</template>

<script>
import TreeNode from './TreeNode.vue';

export default {
  components: {
    TreeNode
  },
  data() {
    return {
      treeData: [
        // 树形数据结构
      ]
    }
  },
  methods: {
    handleCheck(checked) {
      console.log('节点勾选状态:', checked);
    }
  }
}
</script>
  1. 调试和优化:完成功能实现后,进行测试和调试,确保功能的正确性和稳定性。根据实际需求进行优化,提升代码的性能和用户体验。

  2. 优化展开和收起功能:在上一步的示例中,我们使用了v-show指令来显示和隐藏子节点。但是这种方式只是将子节点的DOM元素隐藏,并没有真正地移除或销毁。如果树形结构非常大,可能会导致性能问题。为了优化展开和收起功能,我们可以使用动态组件<component>来实现。

首先,在TreeNode组件中添加一个component属性,用于存储要渲染的子组件的名称:

<template>
  <div>
    <div @click="toggle">
      {{ node.label }}
      <input type="checkbox" v-model="isChecked" />
    </div>
    <component :is="component" v-if="isExpanded"></component>
  </div>
</template>

<script>
export default {
  name: 'TreeNode',
  // ...
  computed: {
    component() {
      return this.isExpanded ? 'TreeNodes' : null;
    }
  }
}
</script>

然后,在父组件中创建一个名为TreeNodes的组件,该组件递归调用自身来渲染子节点:

<template>
  <div>
    <tree-node v-for="node in node.children" :node="node" :key="node.id" @update:checked="handleCheck"></tree-node>
  </div>
</template>

<script>
import TreeNode from './TreeNode.vue';

export default {
  name: 'TreeNodes',
  components: {
    TreeNode
  },
  props: ['node'],
  methods: {
    handleCheck(checked) {
      console.log('节点勾选状态:', checked);
    }
  }
}
</script>

通过使用动态组件,我们可以在展开和收起时动态地创建或销毁子节点的组件实例,从而提高性能。

  1. 添加默认展开或勾选功能:如果需要在初始化时就展开一些节点或者默认勾选一些节点,可以在父组件中对treeData数据进行修改。例如:
data() {
  return {
    treeData: [
      {
        id: 1,
        label: '节点1',
        children: [],
        isExpanded: true, // 默认展开
        isChecked: false // 默认不勾选
      },
      // ...
    ]
  }
}

通过设置节点的isExpandedisChecked属性来实现默认展开和勾选功能。

  1. 其他功能扩展:除了上述的基本功能之外,树状收展及勾选联动还有很多其他的扩展功能,例如搜索节点、拖拽节点、编辑节点等。您可以根据项目需求自行扩展或使用第三方库来实现这些功能。