核心是让递归可中断、可复用、可精准比对,依赖vnode结构设计(含稳定key与children数组)和patch优化(跳过空children比对、key索引复用、最小化更新)。

核心不在“递归”本身,而在让递归可中断、可复用、可精准比对。关键靠 vnode 结构设计 + patch 逻辑优化,避免全量遍历和无效重绘。
vnode 必须带 key 和 children 字段
每个树节点生成 vnode 时,key 要稳定唯一(推荐用 id + 路径 拼接),不能用数组 index;children 必须是 vnode 数组,哪怕当前节点已收起——此时设为空数组 [],而非 undefined 或直接省略。这样 patch 才能识别“只是子内容变了”,而不是“整个子结构被删了再重建”。
renderNode 按状态条件递归
渲染函数不无脑展开所有层级:
- 若节点
isExpanded === true,才调用renderNode生成子 vnode 列表 - 若为
false,children设为空数组,但该节点 vnode 仍存在,保持父节点结构完整 - 节点自身 vnode 包含 icon、label、toggle 事件等 UI 属性,与业务数据解耦
patch 阶段跳过无效比对
标准 patch 默认深度遍历 children,但树形控件常达千级节点,必须剪枝:
- 当新旧 vnode 的
children都是空数组,直接跳过子比对,不进递归 - 当 children 非空,先比 key 序列:key 相同则复用旧 vnode 实例(通过
sameVnode判断),只更新 props 或 text - 新增/删除子节点时,用 key 建 map 索引,实现 O(1) 查找,避免暴力遍历匹配
展开/折叠只触发最小更新
不要整树 re-render:
- 仅更新被操作节点的
isExpanded状态 - 仅重新生成该节点的 vnode(
children从[]变为子 vnode 数组,或反之) - patch 自动识别:仅
children引用变化 → 复用原 vnode 其他属性,只替换子引用 - 真实 DOM 中,只插入或移除对应的一批子元素,父节点和兄弟节点完全不动


















