uni-app递归组件必须用v-if而非v-show,name须与文件名严格一致且不可省略,v-for的key必须为唯一id,父子通信禁用$parent/$children而应使用props+emit或uni.$emit/uni.$on。

uni-app里递归组件必须用v-if而不是v-show
递归组件在 uni-app 中无法正常渲染,大概率是因为用了 v-show。它只是切 display,不销毁子组件实例,而递归组件依赖父子实例的完整生命周期来控制层级展开/收起;一旦子组件没被真正卸载,data、watch 和 onLoad 行为就会错乱,导致节点重复渲染或点击失效。
实操建议:
- 所有递归调用处统一用
v-if="item.children && item.children.length"控制子树是否挂载 - 避免在递归组件内部再用
v-show切换自身可见性——这会破坏递归结构的响应式边界 - 如果需要“视觉隐藏但保留状态”,应改用 CSS
visibility: hidden+ 手动控制height或overflow,而非依赖 Vue 指令
递归组件的name选项不能省,且必须和文件名一致
uni-app(尤其在微信小程序平台)对递归组件的解析非常依赖显式声明的 name。如果组件没写 name,或者写了但和 .vue 文件名不一致(比如文件叫 TreeItem.vue,却写 name: 'TreeNode'),编译时可能报 Component is not found,或在真机上子节点完全不渲染。
实操建议:
- 在组件
export default里强制声明name: 'TreeItem'(与文件名严格一致) - 模板中递归调用时写成
<treeitem v-for="child in item.children" :key="child.id" :item="child"></treeitem>,不要用 kebab-case 形式如<tree-item></tree-item>,小程序平台不识别 - H5 端虽宽松些,但为跨端一致性,始终用 PascalCase 调用
父子节点通信别用$parent或$children,用事件总线或 props+emit
递归组件层级深了之后,$parent 容易指向错层(比如跳过中间某级),$children 在 uni-app 中不稳定且不可预测——尤其在 v-if 切换后,子组件实例顺序可能变化,索引取值直接出错。
实操建议:
- 父节点通过
:expanded.sync="item.expanded"或:expanded="item.expanded"+@toggle="val => item.expanded = val"控制展开态 - 点击节点选中时,用
this.$emit('select', item)向上传递,由最外层菜单容器统一处理高亮逻辑 - 如需跨多层通知(比如折叠全部子节点),推荐用
uni.$emit/uni.$on,比 Vuex 更轻量,也避开了递归组件中 store 实例获取时机问题
性能敏感场景下,必须加key且避免用index
树形菜单常伴随动态增删、拖拽排序,若 v-for 的 key 写成 :key="index",节点移动后 Vue 会复用旧组件实例,导致 data 错位、watch 失效、动画异常——最典型的是“点了 A 节点,B 节点却展开了”。
实操建议:
- 每个节点必须有唯一稳定标识,优先用后端返回的
id字段::key="item.id" - 没有服务端 id 时,可用路径拼接生成(如
item.path || [parentPath, item.label].join('-')),确保同级不重复 - 展开/收起动画建议用
transform: scaleY()+overflow: hidden,比v-if切换更顺滑;但注意:动画期间不能触发scroll-view的滚动条重算,否则卡顿明显


















