adapted reproduction

key 的力量:组件身份与重挂载

复现 Framer Motion keys 教程的核心实验,比较普通属性更新与 key 改变后的状态、生命周期和入场动画。

问题

React 的 key 常被误解为“消除列表警告的字段”,但它实际参与组件身份判断。若同一位置的 key 改变,React 不会把它当成旧实例的普通更新,而会卸载旧实例并挂载新实例。这个差异为什么能触发动画,又为什么会清空局部状态?

组件身份实验这里用原生 DOM 模拟 React 的身份与重挂载语义。

没有 key · update

0
mount(甲)

key=name · remount

0
mount(甲)

先给两边计数,再切换 name:左侧保留状态,右侧重挂载并归零。

直觉

先给计数器加几次,再切换名字。未启用 key 时,名字只是 props:外观改变,计数仍属于同一个组件实例。启用“使用 name 作为 key”后再次切换,身份也随名字改变,于是旧计数器退出、新计数器从零进入。

Framer Motion 的入场动画发生在挂载时,AnimatePresence 则为卸载提供退出阶段。改变 key 会重新触发这组生命周期,因此它可以成为明确的动画重播开关,而不需要在每个动画内部维护一套“重新开始”状态。

形式化

可把一个渲染位置上的身份简化成二元组:

identity=(component type,key).identity=(component\ type,key).

若新旧 identity 相同,执行 update 并保留实例状态;若不同,则执行 unmount(old)mount(new)。动画库只是订阅了挂载与卸载的可见结果,key 本身并不“播放动画”。

结果

实验把计数、名字和生命周期日志并排显示。最重要的观察不是卡片移动,而是同一次“切换名字”在两种身份策略下产生不同状态轨迹:update 保留计数,unmount → mount 归零并触发入场效果。

限制

本页为避免给研究室引入 React 和 Framer Motion,使用原生 DOM 明确模拟其身份语义;它不是 React reconciler 的实现。真实应用还要考虑列表稳定性、焦点丢失、表单状态和退出动画时序,不能把随机 key 当作通用刷新按钮。

来源