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

IT技术 javascript reactjs
2021-01-22 19:59:20

我正在构建一个接受 JSON 数据源并创建一个可排序表的 React 组件。
每个动态数据行都有一个分配给它的唯一键,但我仍然收到以下错误:

数组中的每个孩子都应该有一个唯一的“key”props。
检查TableComponent 的render 方法。

我的TableComponent渲染方法返回:

<table>
  <thead key="thead">
    <TableHeader columns={columnNames}/>
  </thead>
  <tbody key="tbody">
    { rows }
  </tbody>
</table>

TableHeader组件是单行,并且还分配有唯一键。

每个rowinrows都是由一个具有唯一键的组件构建的:

<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>
    )
  }
});

是什么导致了唯一键props错误?

6个回答

您应该为每个孩子以及 children 中的每个元素添加一个键

这样 React 可以处理最小的 DOM 更改。

在您的代码中,每个人<TableRowItem key={item.id} data={item} columns={columnNames}/>都试图在没有密钥的情况下在其中呈现一些孩子。

检查这个例子

尝试key={i}<b></b>div 内元素中删除。(并检查控制台)。

在示例中,如果我们不给钥匙<b>,而我们也希望更新object.city起react需要重新渲染整个行VS只是元素。

这是代码:

var data = [{name:'Jhon', age:28, city:'HO'},
            {name:'Onhj', age:82, city:'HN'},
            {name:'Nohj', age:41, city:'IT'}
           ];

var Hello = React.createClass({

    render: function() {

      var _data = this.props.info;
      console.log(_data);
      return(
        <div>
            {_data.map(function(object, i){
               return <div className={"row"} key={i}> 
                          {[ object.name ,
                             // remove the key
                             <b className="fosfo" key={i}> {object.city} </b> , 
                             object.age
                          ]}
                      </div>; 
             })}
        </div>
       );
    }
});

React.render(<Hello info={data} />, document.body);

@Chris在底部发布的答案比这个答案更详细。请看https://stackoverflow.com/a/43892905/2325522

关于密钥在和解中的重要性的 React 文档:Keys

是否有文档解释了为什么不仅孩子节点都需要一个唯一的关键,也是孩子们的子节点?我在文档中找不到任何关于此的具体内容。
2021-03-13 19:59:20
2021-03-23 19:59:20
这几乎是上面链接的问题聊天中的注释中的官方文字:键是关于集合成员的身份,并且为从任意迭代器中出现的项目自动生成键可能对 React 中的性能有影响图书馆。
2021-03-24 19:59:20
我遇到了完全相同的错误。聊天后解决了吗?如果是这样,您能否发布此问题的更新。
2021-04-08 19:59:20
为什么 React 自己生成唯一键如此困难?
2021-04-08 19:59:20

迭代数组时要小心!!

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

Each child in an array should have a unique "key" prop.

然而,在许多情况下并非如此!这是一种反模式,在某些情况下会导致不需要的行为


理解keyprops

React 使用keyprop 来理解组件到 DOM 元素的关系,然后用于协调过程因此,键始终保持唯一性非常重要,否则 React 很可能会混淆元素并改变不正确的元素。为了保持最佳性能,这些键在所有重新渲染过程中保持静态也很重要

话虽如此,只要知道数组是完全静态的并不总是需要应用上述内容但是,鼓励尽可能应用最佳实践。

一位 React 开发人员在这个 GitHub 问题中说

  • 关键不是真的关于性能,它更多的是关于身份(这反过来会导致更好的性能)。随机分配和变化的值不是身份
  • 在不知道您的数据是如何建模的情况下,我们实际上无法[自动] 提供密钥。如果您没有 id,我建议您使用某种散列函数
  • 当我们使用数组时,我们已经有了内部键,但它们是数组中的索引。当您插入一个新元素时,这些键是错误的。

简而言之,akey应该是:

  • 唯一- 键不能与同级组件的键相同
  • 静态- 渲染之间不应更改键。

使用keyprops

根据上面的解释,仔细研究以下示例,并在可能的情况下尝试实施推荐的方法。


坏(可能)

<tbody>
    {rows.map((row, i) => {
        return <ObjectRow key={i} />;
    })}
</tbody>

这可以说是在 React 中迭代数组时最常见的错误。这种方法在技术上并没有“错误”,它只是……如果您不知道自己在做什么,则是“危险的”如果您正在遍历静态数组,那么这是一种完全有效的方法(例如,导航菜单中的链接数组)。但是,如果您要添加、删除、重新排序或过滤项目,则需要小心。看看官方文档中的这个详细解释

在这个片段中,我们使用了一个非静态数组,我们不限制自己将它用作堆栈。这是一种不安全的方法(您会明白为什么)。请注意,当我们将项目添加到数组的开头(基本上是 unshift)时,每个项目的值<input>保持不变。为什么?因为key不唯一标识每个项目。

换句话说,起初Item 1key={0}当我们添加第二个项目时,最上面的项目变成Item 2,然后Item 1是第二个项目。然而,现在Item 1key={1}key={0}不再有。相反,Item 2现在有key={0}!!

因此,React 认为<input>元素没有改变,因为Itemwith 键0总是在顶部!

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

只有在以某种方式过滤、重新排列数组或添加/删除项目时,这种方法才有风险。如果它始终是静态的,那么使用它是完全安全的。例如,["Home", "Products", "Contact us"]可以使用此方法安全地迭代导航菜单,因为您可能永远不会添加新链接或重新排列它们。

简而言之,此时您可以安全地将索引用作key

  • 数组是静态的,永远不会改变。
  • 从不过滤数组(显示数组的子集)。
  • 数组永远不会重新排序。
  • 该数组用作堆栈或 LIFO(后进先出)。换句话说,添加只能在数组的末尾进行(即推送),并且只能删除最后一项(即弹出)。

如果我们在上面的代码片段中将添加的项目送到数组的末尾,则每个现有项目的顺序将始终是正确的。


很坏

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={Math.random()} />;
    })}
</tbody>

虽然这种方法可能会保证键的唯一性,但它总是会强制 react 重新渲染列表中的每个项目,即使这不是必需的。这是一个非常糟糕的解决方案,因为它极大地影响了性能。更不用说在Math.random()两次产生相同数字的情况下不能排除密钥冲突的可能性

不稳定的键(如由 产生的键Math.random())将导致不必要地重新创建许多组件实例和 DOM 节点,这可能会导致子组件的性能下降和状态丢失。


非常好

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uniqueId} />;
    })}
</tbody>

这可以说是最好的方法,因为它使用数据集中每个项目唯一的属性。例如,如果rows包含从数据库中获取的数据,则可以使用表的主键(通常是自动递增的数字)。

选择键的最佳方法是使用一个字符串,该字符串在其兄弟项中唯一标识列表项。大多数情况下,您会使用数据中的 ID 作为键


好的

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

@skube,是的,这是完全可以接受的。如上面的示例所述,您可以使用迭代数组的项目索引(这是一个整数)。甚至文档说明:“作为最后的手段,您可以将数组中项目的索引作为键传递”然而发生的事情是key无论如何最终总是一个字符串。
2021-03-10 19:59:20
@farmcommand2,键应用于 React 组件,并且需要在兄弟组件中是唯一的这是上面所说的。换句话说,在数组中唯一
2021-03-11 19:59:20
官方文档中,他们使用toString()转换为字符串而不是保留为数字。记住这一点很重要吗?
2021-03-24 19:59:20
我想你可以使用整数,但应该吗?根据他们的文档,他们声明“......选择密钥的最佳方法是使用唯一标识......字符串”(强调我的)
2021-03-29 19:59:20
@skube,不,您也可以使用整数key不知道他们为什么要转换它。
2021-04-01 19:59:20

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

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

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

这非常有帮助,谢谢!我什至没有意识到我必须把它放在最外层
2021-03-09 19:59:20
<> 是 <React.Fragment> 的简短语法。所以如果你想添加一个键,你可以这样做: <React.Fragment key={item.id}>
2021-03-11 19:59:20
谢谢你。这对我有帮助。我在 map() 中使用 <></> 删除对我有帮助。
2021-03-16 19:59:20
我在使用 Material-UI 时遇到了同样的问题,其中我使用了列表(以及其他地方的菜单)并将这些组件的子项放置在工具提示中。给工具提示“键”而不是 ListItem/MenuItem 本身消除了警告。
2021-03-28 19:59:20
我正在做类似的分组,但使用表格行。您不能在 div 中包装表格行。相反,将它包装在一个带有键的 react Fragment 中,如下所示:stackoverflow.com/a/51875412/750914
2021-03-31 19:59:20

检查:key = undef !!!

您还收到警告消息:

Each child in a list should have a unique "key" prop.

如果您的代码完整正确,但如果打开

<ObjectRow key={someValue} />

someValue 未定义!!!请先检查这个。您可以节省数小时。

只需将唯一键添加到您的组件中

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})