必须用自定义递归组件(如org-tree-node)配合v-for和唯一:key实现,通过Map独立维护各节点展开状态、透传完整上下文事件、按需加载子节点、初始化children: []并用$set更新,避免爆栈与响应失效。

uni-app 里怎么用递归组件渲染多层级组织架构树
直接说结论:必须用 <recursive></recursive> 自定义组件 + v-for + :key 唯一标识,不能靠 uni.showActionSheet 或原生 tree 标签——uni-app 没这玩意儿,得自己搭。
核心难点不在“能不能递归”,而在“递归时子节点数据怎么传、展开状态怎么独立维护、点击事件怎么精准触发到某一层级”。很多人卡在子组件拿不到父级传来的 item 或 level,或者所有节点共用一个 expanded 变量,一点全开。
- 必须给每层节点分配独立的展开状态,推荐用
Map或对象键为id存布尔值(如{'1': true, '2': false}) -
v-for循环子节点时,:key必须包含层级路径(比如item.id + '-' + level),否则 uni-app 的 diff 算法容易复用错误实例 - 不要在递归组件内部直接修改 props,要用
$emit('update:xxx')或通过事件通知父组件更新状态
递归组件怎么写才不爆栈、不重复渲染
常见错误是把整个树结构一次性塞进 data,然后在模板里无限制 v-for 嵌套;uni-app 在低端安卓机上很容易卡死或白屏。实际要控制“按需展开”——初始只加载一级,子节点数据延迟获取或预置在 children 字段里但默认不渲染。
- 组件自身必须有
name(如name: 'org-tree-node'),否则<component :is="..."></component>或直接引用都会失效 - 递归调用写法只能是:
<org-tree-node v-for="child in item.children" :key="child.id" :item="child" :level="level + 1"></org-tree-node>,不能用<component :is="'org-tree-node'" ...></component>—— 这种写法在 H5 和小程序平台行为不一致 - 如果后端返回的是扁平化数据(带
pid和id),务必在onLoad里用 JS 手动构建成树形结构,别指望v-for自动嵌套
点击某个节点时,怎么准确知道它是第几层、属于哪个部门、有没有子节点
关键不是“怎么取层级”,而是“怎么让事件携带足够上下文”。很多开发者只传 item.id,结果处理逻辑里还得反复查数组找父节点,既慢又易错。
- 在递归组件中触发点击事件时,直接透传完整
item对象 + 当前level+ 路径数组(如['1', '5', '12']),写法:@node-click="e => handleNodeClick(e, item, level, [...path, item.id])" - 判断是否有子节点,别只看
item.children?.length,要结合item.hasChildren(后端字段)或item.children === null(表示未加载过),避免空数组导致箭头图标一直显示“可展开” - 展开/收起图标状态建议用
computed计算:isExpandable() { return this.item.hasChildren || (this.item.children && this.item.children.length) }
小程序平台下 watch 监听 children 不生效?
这是真坑:在微信小程序或支付宝小程序编译模式下,watch 对深层嵌套对象(比如 item.children[0].children)默认不触发,因为 uni-app 的响应式系统对动态添加属性支持弱。
- 初始化时确保每个节点都有
children: [](哪怕为空数组),不要留children: undefined - 后续追加子节点必须用
this.$set(item, 'children', newData)或Vue.set(uni-app 2.x);直接赋值item.children = [...]不会触发更新 - 如果用了 Composition API(
setup),记得用markRaw包裹原始数据再传入递归组件,否则响应式代理可能干扰子组件内部逻辑
最麻烦的其实是跨平台样式对齐:H5 上能用 margin-left 控制缩进,小程序里得靠 padding-left 加固定宽度 icon 占位,否则层级深了会挤出屏幕。这个细节没文档提,但上线前必踩。










