以观书法
108.85M · 2026-02-05
在Vue开发中,我们经常会遇到「层级不固定」的结构渲染需求——比如树形菜单、分类列表、文件夹导航、评论嵌套等。这类场景如果用循环嵌套写,层级越多代码越冗余,维护成本极高。
而递归组件,就是解决这类问题的“最优解”。它的核心逻辑是「组件自身调用自身」,而Vue中最经典、最易用的递归写法,就是 name+template递归(无需额外引入组件,仅靠组件name属性即可实现自调用),也是面试高频考点。
很多新手会疑惑:“组件怎么能自己调用自己?不会报错吗?” 其实关键就两个点,理解后就能轻松上手:
简单总结:给组件定义name → 在template中,通过name值调用组件自身 → 加终止条件,避免无限递归,这就是name+template递归的完整逻辑。
结合最常用的「树形菜单」场景,手把手实现递归组件,代码可直接复制套用,重点看注释细节。
先定义一份模拟的树形菜单数据,层级可任意扩展(这里是3级,实际开发中可动态获取):
// 模拟树形菜单数据(层级不固定,可无限扩展)
const treeData = [
{
id: 1,
label: "首页",
children: [ // 子节点(有子节点则继续递归)
{ id: 11, label: "首页banner", children: [] },
{ id: 12, label: "首页导航", children: [] }
]
},
{
id: 2,
label: "商品管理",
children: [
{
id: 21,
label: "商品列表",
children: [ // 三级子节点
{ id: 211, label: "上架商品" },
{ id: 212, label: "下架商品" }
]
},
{ id: 22, label: "商品分类" }
]
},
{ id: 3, label: "个人中心", children: [] } // 无子女节点(终止递归)
];
组件核心:定义name属性 → template中自调用 → 加终止条件(判断children是否为空),分选项式API和组合式API(
<template>
<ul class="tree-menu">
<!-- 循环当前层级数据 -->
<li v-for="item in menuData" :key="item.id" class="tree-item">
<!-- 渲染当前节点内容 -->
<span>{{ item.label }}</span>
<!-- 递归核心:自身调用自身(通过name值TreeMenu) -->
<!-- 终止条件:只有当children存在且长度>0时,才递归调用 -->
<TreeMenu
v-if="item.children && item.children.length"
:menu-data="item.children"
/>
</li>
</ul>
</template>
<script>
export default {
// 1. 定义name属性(必须,作为组件自引用标识)
name: "TreeMenu",
// 2. 接收父组件传递的当前层级数据(递归时,传递子节点数据)
props: {
menuData: {
type: Array,
default: () => []
}
}
};
</script>
<script setup>,Vue3首选)注意:<script setup> 中,组件默认没有name属性,需要手动通过defineOptions定义,其他逻辑和选项式API一致。
<template>
<ul class="tree-menu">
<li v-for="item in menuData" :key="item.id" class="tree-item">
<span>{{ item.label }}</span>
<!-- 同样通过name值TreeMenu自调用 -->
<TreeMenu
v-if="item.children && item.children.length"
:menu-data="item.children"
/>
</li>
</ul>
</template>
<script setup>
// Vue3 <script setup> 需手动定义name(Vue3.3+支持)
defineOptions({
name: "TreeMenu" // 核心:自引用标识
});
// 接收父组件传递的层级数据
const props = defineProps({
menuData: {
type: Array,
default: () => []
}
});
</script>
使用方式和普通组件完全一致,只需传递最外层数据即可,组件会自动递归渲染所有层级:
<template>
<div class="app">
<h2>树形菜单(递归组件实现)</h2>
<TreeMenu :menu-data="treeData" />
</div>
</template>
<script setup>
import TreeMenu from "./components/TreeMenu.vue";
// 导入模拟的树形数据
const treeData = [/* 上文定义的树形数据 */];
</script>
坑点:如果忘记加v-if判断,组件会无限调用自身,导致栈溢出、页面崩溃,控制台会报“Maximum call stack size exceeded”错误。
解决方案:固定写法——v-if="item.children && item.children.length",只有存在子节点时才递归,无子女节点则终止。
递归时,父组件给子组件传递的必须是「当前节点的children」(:menu-data="item.children"),而不是整个树形数据,否则会重复渲染所有层级,导致数据错乱。
递归组件会渲染多个层级,样式容易继承混乱(比如所有li标签样式一致,无法区分层级)。
解决方案:通过嵌套样式给不同层级添加区分(比如子层级缩进),示例:
<style scoped>
.tree-menu {
margin-left: 20px; /* 子层级缩进,区分层级 */
}
.tree-item {
margin: 5px 0;
list-style: none;
}
.tree-item span {
cursor: pointer;
}
.tree-item span:hover {
color: #42b983;
}
</style>
除了树形菜单,name+template递归还能适配以下高频场景,核心逻辑完全一致,只需修改渲染内容即可:
数据结构中,每条评论包含id、content、reply(子评论数组),递归渲染reply数组,终止条件:reply为空时停止。
数据结构包含folderName、isFolder、children(子文件夹),递归渲染children,可添加展开/折叠逻辑(v-show控制子层级显示隐藏)。
和树形菜单逻辑一致,可添加选中状态、跳转链接,适配电商平台的多级分类导航。
其实Vue递归组件一点都不复杂,name+template写法更是入门级难度,记住“自引用+终止条件”两个核心,就能轻松应对所有层级渲染需求。练会这个案例,不管是实际开发还是面试,都能轻松拿捏~