
在 React 18.2 中,key 属性写在 JSX 元素上时,若与扩展运算符({...{}})顺序不同,可能导致意外的 key 丢失或失效,进而触发「Each child in a list should have a unique "key" prop」警告——根本原因在于 React 对 key 的特殊处理逻辑及 JSX 编译规则。
在 react 18.2 中,`key` 属性写在 jsx 元素上时,若与扩展运算符(`{...{}}`)顺序不同,可能导致意外的 key 丢失或失效,进而触发「each child in a list should have a unique "key" prop」警告——根本原因在于 react 对 `key` 的特殊处理逻辑及 jsx 编译规则。
React 的 key 并非普通 props:它是一个保留属性(reserved prop),由 React 运行时专门识别和提取,必须直接出现在 JSX 标签名后、属性列表最前方(或至少不能被 spread 操作覆盖)。当使用 {...{}} 扩展空对象时,JSX 编译器(如 Babel)会将其转换为 Object.assign({}, {}, props) 形式;而 React 在创建元素时,会优先从显式声明的 props 中读取 key,若 key 出现在 spread 表达式之后,则可能被后续同名属性覆盖(尽管空对象不包含 key),但更关键的是:Babel 的 JSX 转换规则规定 —— key 必须是 JSX 标签的「静态属性」,不能是 spread 的一部分,否则会被忽略。
验证这一点的最简示例:
// ❌ 错误:key 被 spread 遮蔽(即使 spread 是空对象)
<div key="formKey">
<input><button>Test</button>
</div>
// 编译后等价于 React.createElement('div', { key: 'formKey', ...{} }, ...),
// 但实际 Babel 处理中,spread 会合并到 props 对象,而 key 若未作为独立 prop 显式前置,可能无法被正确注入。
// ✅ 正确:key 显式置于 spread 前,确保被 React 优先捕获
<div key="formKey">
<input><button>Test</button>
</div>
// 编译后 React.createElement 自动将 key 提取为独立参数:React.createElement('div', { key: 'formKey' }, ...)
⚠️ 注意事项:
-
key永远不能通过props={{ key: 'xxx' }}或{...{ key: 'xxx' }}动态传入 —— 它必须是 JSX 标签的直接属性; - 当组件被用于
map()列表渲染时,key必须唯一且稳定(如item.id),而非硬编码常量(如"formKey"),否则 React 无法区分兄弟节点,仍会报错; - 单个元素(无兄弟同级节点)通常不触发该警告,因为 warning 仅在
React.Children.toArray()或React.createElement处理子元素数组时校验 key —— 但若该div本身是某个map的返回项,且key未正确绑定,则必然触发。
✅ 最佳实践总结:
- 始终将
key写在 JSX 开始标签的最左侧显式位置; - 避免在
key前使用任何 spread 语法({...props}); - 列表渲染中,
key值必须来自数据源(如user.id),禁止使用索引(index)或固定字符串; - 可借助 ESLint 插件
eslint-plugin-react的react/jsx-key规则提前发现此类问题。
此行为与 React 版本无关(16.x 至 18.x 均一致),本质是 JSX 语法规范与 React 元素构造机制共同决定的设计约束。











