有这样一个数组数据,实际可能有很多级。
tree: [
{
id: 1,
name: '1',
children: [
{
id: 2,
name: '1-1',
children: [
{
id: 7,
name: '1-1-1',
children: []
}
]
},
{ id: 3, name: '1-2' }
]
},
{
id: 4,
name: '2',
children: [
{ id: 5, name: '2-1' },
{ id: 6, name: '2-2' }
]
}
]
要渲染为下面这种树形
应该很容易想到使用递归组件,下面就来写一个tree
组件。简单用vue2
实现一下。
用法很简单
传入tree数据,支持返回当前点击的节点。
实现一下
先写一个tree
组件,在components/tree/index.vue
<template>
<div>
<div
class="node"
v-for="node in data"
:key="node.id"
>
<tree-node
:node="node"
@node-click="nodeClick"
>
</tree-node>
</div>
</div>
</template>
<script>
import treeNode from './tree-node.vue'
export default {
components: { treeNode },
props: {
data: {
type: Array,
default: () => []
}
},
methods: {
nodeClick(node) {
this.$emit('node-click', node)
}
}
}
</script>
因为父组件页面中传进来的是个数组,需要先循环这个数组,然后递归渲染每一个子节点,我们就再写一个tree-node
组件,专门用来渲染子节点,核心代码递归也是在这个组件中完成的。
同级目录写一个tree-node
组件,在components/tree/tree-node.vue
<template>
<div>
<!-- 避免冒泡到父组件,要加.stop -->
<div
class="node-name"
@click.stop="nodeClick"
>
{{node.name}}
</div>
<!-- 有children就遍历,递归渲染自身,把每一项传进去 -->
<div
class="children"
v-if="node.children && node.children.length"
>
<tree-node
v-for="item in node.children"
:key="item.id"
:node="item"
v-on="$listeners"
>
</tree-node>
</div>
</div>
</template>
<script>
export default {
name: 'tree-node',
props: {
node: Object
},
methods: {
nodeClick() {
// 因为传入当前组件的是每一级的节点
// 只需要再传回去就好了
this.$emit('node-click', this.node)
}
}
}
</script>
<style scoped>
.node-name {
cursor: pointer;
}
.children {
padding-left: 20px;
}
</style>
划重点、抛出问题
- 递归的核心是自己调用自己,但要注意边界条件,否则会进入死循环,内存泄漏。对应到以上代码就是
v-if="node.children && node.children.length"
。 - 以上代码已经实现了递归渲染,但只是
vue2
的实现。你可能注意到其中有一个很重要的语句,v-on="$listeners"
,如果不加会导致从第二层递归开始直到最底层,都无法使用emit
向父组件传递事件,因为每一级节点的父组件都是自身,需要传给子组件一个事件,子组件才能使用emit
调用。
那么还有其他方式解决以上问题吗
这就不得不说说uni-app和vue3了,顺便也说说uni-app和vue在递归组件这方面的不同点。
- uni-app不支持
$listeners
,vue3也移除了$listeners
,那如何解决呢?下面是不使用$listeners
的做法,在v2、uni-app和v3中都适用,无非是v3使用了组合式api。注意看注释,有三个改动点:
<template>
<div>
<!-- 改动点1,当前节点数据通过点击事件传递 -->
<div
class="node-name"
@click.stop="nodeClick(node)"
>
{{node.name}}
</div>
<div
class="children"
v-if="node.children && node.children.length"
>
<!--
核心:改动点2,
不再使用$listeners,
每一级都传递一次node-click,
使用nodeClick方法接收参数
-->
<tree-node
v-for="item in node.children"
:key="item.id"
:node="item"
@node-click="nodeClick"
>
</tree-node>
</div>
</div>
</template>
<script>
export default {
name: 'tree-node',
props: {
node: Object
},
methods: {
// 改动点3,通过参数接收数据
nodeClick(node) {
this.$emit('node-click', node)
}
}
}
</script>
<style scoped>
.node-name {
cursor: pointer;
}
.children {
padding-left: 20px;
}
</style>
- 在vue中使用递归组件,无论v2还是v3,都只需要设置一个
name
属性,即可直接调用,在uni-app中除了设置name属性,还需要使用import
引入自身,并使用components
注册组件。
// 引入自身
import treeNode from './tree-node.vue'
export default {
name: 'tree',
components: { treeNode },
props: {
node: Object
},
methods: {
},
}