React 里的 key 不只是列表优化
从一次组件重建问题出发,解释 React key 如何参与组件身份判断,以及什么时候适合用 key 重置状态。
一次排查小程序表单渲染器问题时,遇到一个很典型的场景:组件内部状态处理得不够干净,切换数据后偶尔需要销毁再创建。
在小程序里,直接的做法通常是条件渲染:先让组件消失,下一轮再让它出现。比如先把 a:if 改成 false,再改回 true。这样可以触发卸载和重新创建。
这个问题很容易让人联想到 React:如果想让一个组件重新创建,改一下 key 不就行了吗?
这个说法没错,但容易让人误解 key 的作用。key 最常见的出现场景确实是列表渲染,但它在 React 里的意义不只是“辅助列表 diff”,而是参与判断“这是不是同一个组件”。
English version: React key Is Not Just a List Optimization
key 参与的是组件身份判断
React 会把状态绑定到渲染树里的某个位置。多数情况下,只要同一个组件类型还在同一个位置,React 就会保留它的状态。
如果给组件加上 key,这个 key 就会成为组件身份的一部分:
<Editor key={articleId} article={article} />
当 articleId 改变时,React 不会把它理解成“同一个 Editor 组件收到了一份新 props”,而是理解成“旧的 Editor 被移除,新的 Editor 被创建”。
结果就是:
- 组件内部的
useState会重新初始化; - 子组件树里的状态也会一起重置;
- effect 的清理和重新执行也会按卸载、挂载流程走;
- DOM 节点可能会被重新创建,而不是继续复用。
所以,改 key 不是普通意义上的“重新渲染”,而是更接近“换了一个组件实例”。
重新渲染和重新创建不是一回事
React 组件因为 props 或 state 变化而重新渲染时,组件实例的身份没有变。组件内部的 state 会被保留,effect 会按照依赖变化决定是否重新执行,DOM 也会尽量复用。
key 改变时发生的是另一件事:React 认为组件身份变了。旧组件会走卸载流程,新组件会从初始状态开始。
这也是为什么改 key 常常可以“重置”组件。它不是让组件刷新一下,而是让 React 放弃原来的那棵子树。
什么时候适合用
最适合的场景是:组件内部状态本来就应该跟某个业务身份绑定。
比如聊天窗口切换联系人后,不希望把上一个人的输入草稿保留下来:
<Chat key={to.id} contact={to} />
或者编辑器切换文章后,希望本地草稿、校验状态、光标位置都从新文章开始:
<ArticleEditor key={article.id} article={article} />
这种时候用 key 很自然,因为用户理解里的“对象”已经变了,组件状态也应该跟着换一份。
表单、编辑器、向导页、预览器这类组件尤其常见。只要内部状态属于某个稳定业务对象,用这个对象的 id 作为 key,就是在把组件身份和业务身份对齐。
什么时候不该用
不应该把 key 当成修复状态 bug 的万能开关。
如果组件只要不销毁重建就会出错,通常说明内部状态和外部数据的关系没有理顺。比如 props 变了,但组件内部缓存没有同步;或者表单渲染器把 schema、默认值、用户输入混在了一起。这个时候改 key 能让问题消失,但也可能只是绕开了真正的问题。
尤其不要写这种代码:
<Form key={Date.now()} />
或者:
<Form key={Math.random()} />
这样每次渲染都会让 React 认为组件身份变了。输入框会丢内容,焦点会丢失,effect 会反复执行,性能也会变差。
列表里的 key 也一样,应该优先使用稳定的业务 id。用数组下标做 key 不是绝对不行,但如果列表会插入、删除、排序,就很容易让状态跟错项目。
表单渲染器的问题怎么处理
回到开头的小程序表单渲染器问题,React 里改 key 确实是一种可用手段,但它背后的语义不是“刷新一下组件”,而是“告诉 React 这是另一个组件”。
在小程序或 Vue 的上下文里,key 的行为和 React 不完全一样。要强制重建组件,条件渲染是更直接的办法;更稳妥的做法则是把组件的数据流整理清楚,让它能正确响应外部数据变化。
如果一个表单渲染器必须靠销毁重建才能工作,大概率是组件内部藏了太多不受控状态。短期可以用重建救急,长期还是应该把几件事拆清楚:
- schema 的身份是什么;
- 初始值和用户当前输入如何区分;
- 校验状态、脏状态和提交状态归谁管理;
- 外部数据变化时,是同步已有状态,还是明确执行一次 reset。
这些问题想清楚后,key 仍然可以作为重置手段,但它不再是掩盖状态设计问题的补丁,而是一个表达组件身份变化的工具。
小结
React 里的 key 不只是列表优化。它参与组件身份判断,回答的问题是:当前位置上的组件,还是不是同一个组件?
如果答案是否定的,用稳定的业务 id 作为 key 可以自然地重置状态。如果只是因为组件内部状态处理混乱而想强制销毁重建,就应该先回头看数据流和状态边界。
React 官方文档里有两处相关说明: