React 里的 key 不只是列表优化

最近组里的同学问我,在小程序里想强制重建一个组件要怎么办。

背景是一个表单渲染器内部状态处理得不够干净,切换数据后偶尔需要销毁再创建。我之前也遇到过,处理方式比较直接:用条件渲染让组件先消失再出现。比如先把 a:if 改成 false,下一轮再改回 true

同学说,PC 里的 React 只要改一下 key 就行,小程序不支持吗?

我当时第一反应是:key 不就是列表渲染里用来辅助 diff 的东西吗?

这个理解不算错,但放在 React 里不完整。React 里的 key 确实常见于列表,但它更底层的作用是参与判断“这是不是同一个组件”。

key 描述的是组件身份

React 会把状态绑定到渲染树里的某个位置。多数情况下,只要同一个组件类型还在同一个位置,React 就会保留它的状态。

如果给组件加上 key,这个 key 就会成为组件身份的一部分:

<Editor key={articleId} article={article} />

articleId 改变时,React 不会把它理解成“同一个 Editor 组件收到了一份新 props”,而是理解成“旧的 Editor 被移除,新的 Editor 被创建”。结果就是:

  • 组件内部的 useState 会重新初始化;
  • 子组件树里的状态也会一起重置;
  • effect 的清理和重新执行也会按卸载、挂载流程走;
  • DOM 节点可能会被重新创建,而不是继续复用。

所以,改 key 不是普通意义上的“重新渲染”,而是更接近“换了一个组件实例”。

什么时候适合用

最适合的场景是:组件内部状态本来就应该跟某个业务身份绑定。

比如聊天窗口切换联系人后,不希望把上一个人的输入草稿保留下来:

<Chat key={to.id} contact={to} />

或者编辑器切换文章后,希望本地草稿、校验状态、光标位置都从新文章开始:

<ArticleEditor key={article.id} article={article} />

这种时候用 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、初始值、用户输入和重置逻辑拆清楚。

React 官方文档里有两处相关说明: