侧边菜单、组织架构这类数据天然是树形。与其用模板写死层级循环,不如让组件自己调用自己:一个组件渲染当前层,发现 children 就再渲染一层自己,任意深度都能覆盖。

实现思路

MenuNode 接收 nodes 数组渲染 ul/li;节点带 children 时模板内再写一个 MenuNode 并把子节点传入。Vue3 中 SFC 会按文件名隐式自引用,无需额外 import。

组件代码

<template>
    <ul>
        <li v-for="item in nodes" :key="item.id">
            <div class="node" @click="emit("select", item)">
                {{ item.name }}
            </div>
            <MenuNode
                v-if="item.children && item.children.length"
                :nodes="item.children"
                @select="emit("select", $event)" />
        </li>
    </ul>
</template>

<script setup>
defineProps({
    nodes: { type: Array, default: () => [] }
});
const emit = defineEmits(["select"]);
</script>

使用示例

父组件 import MenuNode 后传根数组即可;点击任意节点通过 @select 拿到整行数据,在父组件里做高亮或路由跳转。

扩展思路

  • 把 ul/li 换成 el-menu 组件并绑定 index 即可驱动菜单导航;
  • 有收起展开需求时在节点上维护 open 状态,点击箭头切换;
  • 默认展开层级可用 defaultOpenLevels prop 由父组件控制。

小结

递归组件一劳永逸解决任意深度树渲染,配合事件冒泡式派发,父子间通信也保持单向数据流,是导航与组织树的标准解法。