了解 React.js 中数组子项的唯一键
我正在构建一个接受 JSON 数据源并创建可排序表的 React 组件。
每个动态数据行都分配有一个唯一键,但我仍然收到以下错误:
Each child in an array should have a unique "key" prop.
Check the render method of TableComponent.
我的
TableComponent
渲染方法返回:
<table>
<thead key="thead">
<TableHeader columns={columnNames}/>
</thead>
<tbody key="tbody">
{ rows }
</tbody>
</table>
TableHeader
组件是一行,并且还分配有一个唯一键。
rows
中的每个
row
都是从具有唯一键的组件构建的:
<TableRowItem key={item.id} data={item} columns={columnNames}/>
并且
TableRowItem
如下所示:
var TableRowItem = React.createClass({
render: function() {
var td = function() {
return this.props.columns.map(function(c) {
return <td key={this.props.data[c]}>{this.props.data[c]}</td>;
}, this);
}.bind(this);
return (
<tr>{ td(this.props.item) }</tr>
)
}
});
是什么导致了唯一键 prop 错误?
您应该为每个孩子添加一个键 以及儿童内部的每个元素 。
这样,反应可以处理最小的dom变化。
在您的代码中,每个
&lt; tableRowitem key = {item.id} data = {item} columns = {columnnames}/&gt;
试图在没有密钥的情况下渲染其中的一些孩子。
检查 示例 。
key = {i}
从
&lt; b&gt;&lt;/b&gt;/b&gt;
element(并检查控制台)。
在示例中,如果我们不给
&lt; b&gt;
元素提供钥匙,我们要更新
object.city
这是代码:
240140097
@chris在底部发布的答案
比此答案更详细。
React React文档密钥的重要性和用法: keys
遍历数组时要小心!
一个常见的误解是,使用数组中元素的索引是抑制您可能熟悉的错误的可接受方式:
Each child in an array should have a unique "key" prop.
然而,在很多情况下并非如此!这是 反模式 ,在 某些 情况下会导致 不良行为 。
理解
key
属性
React 使用
key
属性来理解组件与 DOM 元素的关系,然后将其用于
协调过程
。因此,确保键
始终
保持
唯一
非常重要,否则 React 很可能会混淆元素并改变错误的元素。为了保持最佳性能,这些键在所有重新渲染过程中
保持静态
也很重要。
话虽如此,只要 知道 数组是完全静态的,就 总是 不需要应用上述内容。但是,我们鼓励尽可能地应用最佳实践。
一位 React 开发人员在 此 GitHub 问题 中表示:
- key is not really about performance, it's more about identity (which in turn leads to better performance). randomly assigned and changing values are not identity
- We can't realistically provide keys [automatically] without knowing how your data is modeled. I would suggest maybe using some sort of hashing function if you don't have ids
- We already have internal keys when we use arrays, but they are the index in the array. When you insert a new element, those keys are wrong.
简而言之,
key
应该是:
- 唯一 - 密钥不能与 兄弟组件 的密钥相同。
- 静态 - 密钥在渲染之间永远不应改变。
使用
key
prop
根据上述说明,仔细研究以下示例,并尽可能尝试实施推荐的方法。
不好(潜在)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
这可以说是在 React 中迭代数组时最常见的错误。从技术上讲,这种方法并不是 “错误” ,只是...... “危险” 如果你不知道自己在做什么。如果你正在迭代静态数组,那么这是一种完全有效的方法(例如,导航菜单中的链接数组)。但是,如果您要添加、删除、重新排序或过滤项目,则需要小心。请查看官方文档中的 详细说明 。
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
在此代码片段中,我们使用非静态数组,并且我们并不限制自己将其用作堆栈。这是一种不安全的方法(您会明白为什么)。请注意,当我们将项目添加到数组的开头(基本上是取消移位)时,每个
<input>
的值保持不变。为什么?因为
key
不能唯一地标识每个项目。
换句话说,首先
Item 1
具有
key={0
。当我们添加第二个项目时,顶部项目变为
项目 2
,其次是
项目 1
作为第二个项目。但是,现在
项目 1
具有
key={1
,而不再具有
key={0
。相反,
项目 2
现在具有
key={0
!!
因此,React 认为
<input>
元素没有改变,因为带有键
0
的
Item
始终位于顶部!
那么为什么这种方法 有时 不好呢?
只有在数组以某种方式被过滤、重新排列或项目被添加/删除时,这种方法才有风险。如果它始终是静态的,那么使用它就完全安全了。例如,可以使用此方法安全地迭代导航菜单,例如
["Home", "Products", "Contact us"]
,因为您可能永远不会添加新链接或重新排列它们。
简而言之,以下是您可以
安全地
使用索引作为
键
的情况:
- 数组是静态的,永远不会改变。
- 数组永远不会被过滤(显示数组的子集)。
- 数组永远不会重新排序。
- 数组用作堆栈或 LIFO(后进先出)。换句话说,添加只能在数组末尾进行(即推送),并且只能删除最后一个项目(即弹出)。
如果我们在上面的代码片段中将添加的项目 推送 到数组末尾,则每个现有项目的顺序将始终正确。
非常糟糕
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
虽然这种方法可能会保证键的唯一性,但它
总是
强制重新渲染列表中的每个项目,即使这不是必需的。这是一个非常糟糕的解决方案,因为它极大地影响了性能。更不用说,如果
Math.random()
两次产生相同的数字,就不能排除发生密钥碰撞的可能性。
Unstable keys (like those produced by
Math.random()
) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
非常好
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
这可以说是
最佳方法
,因为它使用了数据集中每个项目唯一的属性。例如,如果
rows
包含从数据库提取的数据,则可以使用表的主键(
通常是一个自动递增的数字
)。
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys
好
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
这也是一个好方法。如果您的数据集不包含任何保证唯一性的数据(
例如任意数字的数组
),则可能会发生密钥冲突。在这种情况下,最好在迭代数据集之前手动为数据集中的每个项目生成唯一标识符。最好在安装组件时或接收数据集时(
例如来自
props
或来自异步 API 调用
)执行此操作,以便只执行
一次
,而不是每次组件重新渲染时都执行此操作。已经有一些库可以为您提供此类密钥。以下是一个例子:
react-key-index
。
这可能对某些人有帮助,也可能没有帮助,但它可能是一个快速参考。这也类似于上面提出的所有答案。
我有很多使用以下结构生成列表的位置:
return (
{myList.map(item => (
<>
<div class="some class">
{item.someProperty}
....
</div>
</>
)}
)
经过一些反复试验(和一些挫折),向最外层块添加一个关键属性解决了它。另请注意,
<>
标签现在已被
<div>
标签替换。
return (
{myList.map((item, index) => (
<div key={index}>
<div class="some class">
{item.someProperty}
....
</div>
</div>
)}
)
当然,在上面的示例中,我一直天真地使用迭代索引(index)来填充键值。理想情况下,您应该使用列表项独有的内容。