transitiongroup动画由渲染器在patch中主动识别增删移操作并注入对应类,enter/leave/move各有独立生命周期;通过key映射与getsequence算法区分节点状态,move为真实dom移动而非视觉位移;enter节点立即插入、leave节点延后卸载以支持交叉动画;key必须唯一稳定,否则导致算法失效和动画错乱。

TransitionGroup 的动画不是靠“自动加类”实现的,而是渲染器在 patch 过程中主动识别列表变化类型,为不同状态的节点注入对应过渡类,并协调 DOM 移动时机。 它不依赖 CSS 动画触发器,而是在虚拟节点更新的每个关键环节插入控制逻辑,让 enter/leave/move 三类动画有各自独立的生命周期。
渲染器如何识别“新增、删除、移动”三类操作
Vue 渲染器对 <transition-group></transition-group> 的子节点不做普通 diff,而是启用专用的 patchChildren 分支(区别于 Fragment 或普通元素):
- 基于新旧子节点数组的
key映射关系,调用getSequence算法计算最长递增子序列,从而区分哪些节点是“原位保留”,哪些是“被移动” - 未出现在新数组中的节点标记为
leaving,进入 leave 阶段;新数组中但旧数组没有的节点标记为entering - 同一节点在新旧数组中索引不同 → 触发
move逻辑,而非先 remove 再 insert
move 动画不靠 CSS transition-delay 模拟,而是真实 DOM 移动
move 类名(如 list-move)生效的前提,是节点在 patch 过程中被 物理移动,而不是视觉位移:
- 渲染器不会把节点先删掉再插到新位置,而是直接调用
hostInsert(el, parent, anchor),其中anchor是它在新顺序中“前一个兄弟节点”的 DOM 元素 - 这个移动过程发生在
patchChildren的最后阶段,在所有 enter/leave 节点处理完毕之后统一执行,确保 move 不干扰其他过渡状态 - CSS 中的
.list-move必须声明transition,否则移动是瞬时的——渲染器只提供移动动作,不接管动画样式
enter 和 leave 的 DOM 存留策略不同
为了支持 enter-from → enter-active → enter-to 和 leave-from → leave-active → leave-to 的完整流程,渲染器对两类节点采用不同挂载/卸载节奏:
-
entering 节点:立即插入 DOM,但初始样式由
enter-from类控制(如opacity: 0),随后浏览器自然触发 transition -
leaving 节点:不立刻从 DOM 移除,而是打上
leaving标记,等leave-to动画结束(通过监听transitionend或 fallback setTimeout)后,才真正调用unmount - 这意味着同一时刻,DOM 中可能同时存在新旧两批节点——这是列表过渡能“交叉播放”的底层保障
为什么 key 必须唯一且稳定
key 是整个 Patch 逻辑的锚点,不是为了 Vue “认出元素”,而是为了让渲染器能精确推导节点身份:
- 如果 key 重复,
getSequence算法失效,move 判断错误,导致节点错位或动画丢失 - 如果 key 动态生成(如
index),数组增删时 key 大量复用,渲染器误判为“移动”而非“增删”,enter/leave 类名不会触发 - Fragment 场景下更敏感:Fragment 无真实 DOM 容器,节点定位全靠 key + anchor,key 错乱直接导致插入位置错误










