
在 Vue 3 仅通过 CDN 引入(无构建工具)的场景中,模板语法无法直接支持自引用递归组件(如 ),必须改用 h() 函数 + defineComponent 的组合式 API 显式声明递归调用。
在 vue 3 仅通过 cdn 引入(无构建工具)的场景中,模板语法无法直接支持自引用递归组件(如 `
Vue 3 的 CDN 版本(vue.global.js)虽支持 Options API 和 Composition API,但其模板编译器在运行时对动态组件名字符串(如 is="menu-comp")的解析存在限制:它无法在未注册全局组件或未显式声明依赖关系的前提下,完成跨层级的组件自我引用,导致递归中断、仅渲染首层节点,并将子组件退化为原生 HTML 标签(如 <menu-comp></menu-comp> 被当作普通标签保留)。
要解决该问题,核心思路是绕过模板编译阶段的名称解析,直接在 JavaScript 层面构造 VNode 树。推荐使用 defineComponent 配合 h() 函数编写函数式渲染逻辑,明确传递组件自身作为子节点的构造器:
<script type="module">
const { createApp, defineComponent, ref, h } = Vue;
const MenuComp = defineComponent({
name: "MenuComp", // 必须显式命名,便于调试与 DevTools 识别
props: ["item"],
setup(props) {
return () => h("li", [
props.item.menuName,
props.item.subMenu && props.item.subMenu.length > 0
? h(
"ul",
props.item.subMenu.map(sub =>
h(MenuComp, { item: sub, key: sub.id })
)
)
: null
]);
}
});
const app = createApp({
setup() {
const message = ref([
{
menuName: "首页",
id: 1,
subMenu: [
{ menuName: "仪表盘", id: 11, subMenu: [] },
{
menuName: "系统管理",
id: 12,
subMenu: [
{ menuName: "用户管理", id: 121, subMenu: [] },
{ menuName: "角色管理", id: 122, subMenu: [] }
]
}
]
}
]);
return { message };
},
components: { "menu-comp": MenuComp }
}).mount("#app");
</script>✅ 关键要点说明:
将静态 Vue 页面(index.vue)改造为动态页面(Custom.vue);稿源仅限 index.vue、其引用的 CSS 与资源路径,禁止参考仓库内其他文件。Invoke when user wants to transform a static Vue page into a dynamic data-driven page with Element UI components.
-
h()是 Vue 3 的底层渲染函数,返回 VNode,完全规避模板解析限制; -
MenuComp在h()调用中直接作为参数传入,形成闭包内显式递归,无需依赖组件注册名字符串; - 每个递归层级必须传入唯一
key(如sub.id),否则 Vue 会警告并可能复用错误节点; -
props.item.subMenu && ...length > 0判断更健壮,避免undefined或null导致.map报错; -
name选项非必需,但强烈建议添加,便于 Vue DevTools 调试和错误堆栈定位。
⚠️ 注意事项:
立即学习“前端免费学习笔记(深入)”;
- 不要在
setup()中使用this(Options API 风格写法已不兼容); -
axios等第三方库需确保在h()渲染逻辑之外调用(如mounted或onMounted),渲染函数应保持纯函数特性; - 若菜单数据来自异步请求,请在
ref初始化为空数组ref([]),并在获取成功后赋值,h()会自动响应式更新。
此方案完全兼容 Vue 3 CDN 环境,无需构建步骤,同时具备良好的可维护性与性能表现,是纯前端轻量级项目实现树形结构(菜单、目录、评论嵌套等)的推荐实践。

















