[Bug] 2.45.x NTree/NSpin 行批量卸载/重建时 patch/unmount 空引用崩溃(2.44.1 正常)
Author: vcqrCreated Sep 17, 2026Updated Sep 17, 2026
环境
- naive-ui:2.45.3(回归从 2.45.0 开始;2.44.1 完全正常)
- vue:3.5.42
- 浏览器:Chromium(Tauri WebView2 与 Chrome 均可复现)
- 场景:主机管理树(数百节点),搜索框逐字符过滤触发
现象
NTree 因搜索过滤导致节点成批卸载/重建时(每次按键 fNodes 变化,或清空搜索还原整树),Vue 的 patch/unmount 阶段抛出一系列 null 引用 TypeError,补丁中断、树视图冻结。
我们先后做了三层规避,全部仍然崩溃:
- 移除外层
NSpin(崩溃归因从 Spin 变为 anonymous 组件); - 移除
virtual-scroll(传统树渲染); - 弃用
pattern/filter/show-irrelevant-nodes,改为应用层数据过滤(直接替换:data为过滤后的新数组)。
错误家族始终不变,归因组件随移除依次变化(Spin → anonymous),说明问题在 2.45 新编译产物内部的 vnode 引用管理,而非某个具体用法组合。
错误签名(五类,均已稳定捕获)
- patch 阶段,旧组件 vnode 已失效(component 为 null):
TypeError: Cannot read properties of null (reading 'emitsOptions')
at shouldUpdateComponent
at updateComponent
at processComponent
at patch
at patchBlockChildren
at patchElement
at processElement
at patch
at ReactiveEffect.componentUpdateFn- patch 阶段,行元素 DOM 已被拆除:
TypeError: Cannot set properties of null (setting '__vnode') // patchElement: el.__vnode
TypeError: Cannot read properties of null (reading 'insertBefore') // mountElement → insert
at insert
at mountElement
at processElement
at patch
at patchBlockChildren
at processFragment
at patch
at patchBlockChildren
at patchElement- unmount 阶段,children 数组中出现 null vnode:
TypeError: Cannot read properties of null (reading 'type')
at unmountComponent
at unmount
at unmountChildren
at unmount (×N)
at unmountComponent
at unmount- patch 阶段,实例未完成挂载:
TypeError: Cannot read properties of null (reading 'subTree')
at getNextHostNode
at patch
at patchBlockChildren
at patchElement
at processElement
at patch
at ReactiveEffect.componentUpdateFn- 报错组件:最初为
Spin,移除 NSpin 后为anonymous(无名字组件,疑似vue-jsx-vapor/vdom.mjs中的 renderList 包装defineComponent),随后大量为树行组件TreeNodeContent/anonymous。
分析(结合堆栈与 2.45 产物代码的推测,供排查方向参考)
2.45.0 起组件经 vue-jsx-vapor/vdom.mjs 编译,怀疑与以下机制在"批量卸载/重建"下的表现有关:
normalizeVNode的createBlock(cloneIfMounted(value)):cloneIfMounted以child.el === null判定是否原样复用 vnode,同一次 flush 内同一 vnode 被多处引用时可能绕过克隆;- 匿名
defineComponent+renderList(patchFlag 128 keyed Fragment)包装的列表渲染; - 组件 vnode 的
dynamicChildren携带 slot 渲染产物,批量变更时 block 元数据长度不稳定,VuepatchElement走 "Force full diff" 路径,期间旧 vnode 的component/el已失效 → 后续 patch/unmount 踩空。
复现要点
真实应用中稳定复现(tauri dev 与浏览器均),要点:
- NTree 数百节点,搜索框逐字符输入,每键改变树数据(无论 naive 的 pattern 过滤,还是应用层数据过滤后替换
:data); - 清空搜索、整树还原的瞬间最容易触发(卸载/挂载量最大);
- 时序相关:数据量大、输入快时必现;小数据量偶发。暂未收缩出独立最小 demo,如需要我们可以继续提供更多信息。
临时缓解
固定 naive-ui 2.44.1(重写前最后一版,产物中无 vue-jsx-vapor),已稳定运行,功能无差异。
Source: tusen-ai/naive-ui