vue实现树形组件
实现树形组件的基本思路
在Vue中实现树形组件通常涉及递归组件和动态数据渲染。核心是通过组件自身调用自身,逐层渲染嵌套的节点数据。
定义树形数据结构
树形数据通常是一个包含children属性的对象数组,每个节点可能有子节点:
const treeData = [
{
id: 1,
label: '节点1',
children: [
{
id: 2,
label: '节点1-1'
}
]
}
]
创建递归组件
创建一个名为TreeNode的组件,该组件能够递归渲染自身:
<template>
<ul>
<li v-for="node in data" :key="node.id">
{{ node.label }}
<TreeNode v-if="node.children" :data="node.children"/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeNode',
props: {
data: {
type: Array,
required: true
}
}
}
</script>
使用树形组件
在主组件中引入并使用TreeNode组件:

<template>
<div>
<TreeNode :data="treeData"/>
</div>
</template>
<script>
import TreeNode from './TreeNode.vue'
export default {
components: { TreeNode },
data() {
return {
treeData: [...]
}
}
}
</script>
添加交互功能
为树形组件添加展开/折叠功能:
<template>
<ul>
<li v-for="node in data" :key="node.id">
<span @click="toggleExpand(node)">
{{ node.expanded ? '▼' : '▶' }} {{ node.label }}
</span>
<TreeNode
v-if="node.children && node.expanded"
:data="node.children"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeNode',
props: {
data: Array
},
methods: {
toggleExpand(node) {
this.$set(node, 'expanded', !node.expanded)
}
}
}
</script>
优化性能
对于大型树结构,可以使用v-show替代v-if来避免重复渲染:
<template>
<ul>
<li v-for="node in data" :key="node.id">
<span @click="toggleExpand(node)">
{{ node.expanded ? '▼' : '▶' }} {{ node.label }}
</span>
<div v-show="node.expanded">
<TreeNode v-if="node.children" :data="node.children"/>
</div>
</li>
</ul>
</template>
添加复选框功能
实现带复选框的树形组件:

<template>
<ul>
<li v-for="node in data" :key="node.id">
<input
type="checkbox"
v-model="node.checked"
@change="handleCheckChange(node)"
>
<span @click="toggleExpand(node)">
{{ node.expanded ? '▼' : '▶' }} {{ node.label }}
</span>
<TreeNode
v-if="node.children && node.expanded"
:data="node.children"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeNode',
props: {
data: Array
},
methods: {
toggleExpand(node) {
this.$set(node, 'expanded', !node.expanded)
},
handleCheckChange(node) {
this.updateChildrenChecked(node, node.checked)
this.updateParentChecked(this.$parent.data)
},
updateChildrenChecked(node, checked) {
if (node.children) {
node.children.forEach(child => {
child.checked = checked
this.updateChildrenChecked(child, checked)
})
}
},
updateParentChecked(nodes) {
nodes.forEach(node => {
if (node.children) {
const allChecked = node.children.every(child => child.checked)
const someChecked = node.children.some(child => child.checked)
node.checked = allChecked ? true : someChecked ? null : false
}
})
}
}
}
</script>
使用插槽自定义节点内容
通过插槽允许自定义每个节点的渲染内容:
<template>
<ul>
<li v-for="node in data" :key="node.id">
<slot name="node" :node="node">
<span @click="toggleExpand(node)">
{{ node.expanded ? '▼' : '▶' }} {{ node.label }}
</span>
</slot>
<TreeNode
v-if="node.children && node.expanded"
:data="node.children"
>
<template v-slot:node="{ node }">
<slot name="node" :node="node"/>
</template>
</TreeNode>
</li>
</ul>
</template>
动态加载子节点
实现异步加载子节点的功能:
<template>
<ul>
<li v-for="node in data" :key="node.id">
<span @click="loadChildren(node)">
{{ node.expanded ? '▼' : '▶' }} {{ node.label }}
</span>
<TreeNode
v-if="node.children && node.expanded"
:data="node.children"
/>
</li>
</ul>
</template>
<script>
export default {
name: 'TreeNode',
props: {
data: Array
},
methods: {
async loadChildren(node) {
if (!node.children) {
const children = await fetchChildren(node.id)
this.$set(node, 'children', children)
}
this.$set(node, 'expanded', !node.expanded)
}
}
}
</script>
样式优化
为树形组件添加基础样式:
ul {
list-style-type: none;
padding-left: 20px;
}
li {
margin: 5px 0;
cursor: pointer;
}
span {
padding: 2px 5px;
}
span:hover {
background-color: #f0f0f0;
}






