递归组件的核心是组件在模板中调用自身并设终止条件以避免无限循环;树形菜单是典型应用,需命名组件(Vue)或自调用(React),通过children存在性控制递归,配合key、懒加载、展开状态等增强实用性。

递归组件的核心在于:组件在模板中调用自身,并通过合理的条件控制递归终止,避免无限循环。树形菜单正是典型场景——每个节点可能包含子节点,子节点结构与父节点一致。
一、理解递归组件的关键前提
Vue 或 React 中实现递归,不是靠“写个函数反复调用”,而是让组件在渲染时根据数据决定是否再次渲染自己。必须满足两个条件:
-
组件需有明确的名称(如 Vue 中
name: 'TreeItem'),否则无法在模板里引用自身 -
必须有终止条件,比如当
children为空数组或 undefined 时,不再渲染子节点部分
二、Vue 中实现树形菜单(组合式 API + 模板递归)
假设数据格式为嵌套对象数组:
```jsconst treeData = [
{ id: 1, label: '首页', children: [] },
{ id: 2, label: '产品', children: [
{ id: 21, label: 'Web 端', children: [] },
{ id: 22, label: '移动端', children: [
{ id: 221, label: 'iOS', children: [] },
{ id: 222, label: 'Android', children: [] }
]}
]},
{ id: 3, label: '关于', children: null }
];
```
创建一个名为 TreeItem.vue 的单文件组件:
- 在
<script setup>中定义 props 接收node和可选的level(用于缩进或样式) - 在模板中渲染当前节点内容,再用
<TreeItem>标签递归渲染node.children中的每一项 - 用
v-if="node.children?.length"控制是否展开子节点区域
三、React 中实现(函数组件 + 自调用)
无需特别命名,直接在组件内部调用自己即可:
```tsxinterface TreeNode {
id: string;
label: string;
children?: TreeNode[];
}
const TreeItem: React.FC = ({ node, level = 0 }) => {
const indent = level * 20;
return (
<div style={{ marginLeft: `${indent}px` }}>
<div className="tree-node">{node.label}</div>
{node.children && node.children.length > 0 && (
<div className="tree-children">
{node.children.map(child => (
<TreeItem key={child.id} node={child} level={level + 1} />
))}
</div>
)}
</div>
);
};
```
注意点:
- 确保每个递归项都有唯一
key,推荐用路径 ID(如`${parentKey}-${child.id}`)避免跨层级 key 冲突 - 避免直接传整个原始数据对象引起不必要的重渲染,可考虑 memo 包裹或使用 useMemo 提取子树
四、实用增强技巧
真实项目中,基础递归只是起点:
-
懒加载子节点:初始只传
hasChildren: true,点击「展开」再请求子数据,用v-if或状态控制是否挂载递归子组件 - 折叠/展开状态管理:用 Map 或对象记录每个节点 ID 对应的展开状态,配合图标切换
- 搜索高亮与自动展开:遍历整棵树匹配关键词,把命中路径上的所有节点设为展开态
- 拖拽排序与嵌套调整:监听 drag/drop 事件,根据目标位置更新数据的嵌套结构,触发重新渲染

















