开发者问题收集

了解 React.js 中数组子项的唯一键

2015-02-04
1346451

我正在构建一个接受 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 错误?

3个回答

您应该为每个孩子添加一个键 以及儿童内部的每个元素

这样,反应可以处理最小的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

jmingov
2015-02-04

遍历数组时要小心!

一个常见的误解是,使用数组中元素的索引是抑制您可能熟悉的错误的可接受方式:

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> 元素没有改变,因为带有键 0Item 始终位于顶部!

那么为什么这种方法 有时 不好呢?

只有在数组以某种方式被过滤、重新排列或项目被添加/删除时,这种方法才有风险。如果它始终是静态的,那么使用它就完全安全了。例如,可以使用此方法安全地迭代导航菜单,例如 ["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

Chris
2017-05-10

这可能对某些人有帮助,也可能没有帮助,但它可能是一个快速参考。这也类似于上面提出的所有答案。

我有很多使用以下结构生成列表的位置:

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)来填充键值。理想情况下,您应该使用列表项独有的内容。

apil.tamang
2020-02-11