用Render函数实现递归文件树需组件声明name、在render中用h()动态创建自身VNode传入子数据,并确保children为非空数组才递归,避免栈溢出;展开状态用顶层ref管理供各层级访问。

用 Render 函数实现递归文件树,核心在于让组件能“调用自己”——不是靠模板里的 <filetree :data="child"></filetree> 这种显式引用,而是通过在 render() 中动态创建当前组件的 VNode,传入子节点数据,形成自调用链。这要求组件必须有名字(name 选项),且递归逻辑要控制好终止条件,否则会无限渲染。
1. 组件必须声明 name,否则无法自引用
Vue 的 Render 函数中,this.$options.name 或直接写字符串名(如 'FileTree')是触发递归的关键。没有 name,Vue 无法在 VNode 中标识该组件类型,也就无法复用自身。
- 在组件定义中明确添加
name: 'FileTree' - 不推荐用
components: { FileTree }局部注册再引用——那属于“兄弟调用”,不是自调用 - 如果使用 setup + defineComponent,需在 defineComponent 选项中写
name: 'FileTree'
2. Render 函数内用 h() 构造自调用 VNode
在 render(h) 或 Composition API 的 render(ctx) 中,对每个子节点调用 h(FileTree, { props: { data: item } }) 即可。这里的 FileTree 就是当前组件自身(依赖 name 匹配)。
- 传入的
props必须包含下一层数据(如item.children),并确保空数组/undefined 时不再递归 - 建议把文件节点结构标准化:每个项含
name、isFolder、children(数组或 undefined) - 可用
h('span', item.name)渲染叶子节点,用h(FileTree, ...)渲染文件夹节点
3. 避免无限递归:守好 base case
递归必须有退出条件,否则浏览器栈溢出。常见错误是把 children: null 当作无子节点,但 null 仍会被当作“有值”,导致继续调用。
- 只在
Array.isArray(item.children) && item.children.length > 0时才生成子组件 VNode - 服务端返回的数据若用
null表示无子节点,渲染前先 normalize:children = children || [] - 可在组件内部加
v-if="data.children?.length"(模板写法)或在 render 中用三元判断跳过 h() 调用
4. 支持展开/折叠状态需要响应式管理
纯静态树无需状态,但真实场景需点击切换子树显隐。此时不能把展开状态存在 props 里(不可变),而应使用 ref 或 reactive 在组件实例上维护。
- 在
setup()中定义const expanded = ref(new Set()),存文件夹 id 或路径 - 点击时
expanded.value.toggle(id),然后触发重渲染 - render 中根据
expanded.value.has(id)决定是否生成子 VNode - 注意:每次递归调用都是独立组件实例,所以
expanded应定义在顶层 setup,供所有层级访问
递归组件不是黑魔法,本质是 Vue 基于 vnode 类型匹配 name 实现的组件复用机制。只要 name 正确、base case 清晰、状态管理得当,Render 函数写出来的文件树比模板更灵活,也更容易封装成可配置的通用组件。

















