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


653

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

数组中的每个子代都应具有唯一的“键”道具。
检查TableComponent的渲染方法。

我的TableComponent渲染方法返回:

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

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

每个row输入rows都是通过具有唯一键的组件构建的:

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

是什么导致独特的按键道具错误?


7
您的JS数组中的行应具有唯一key属性。它将帮助ReactJS查找对适当DOM节点的引用,并仅更新标记内的内容,而不重新渲染整个表/行。
基里尔2015年

您是否也可以共享rows数组或更佳的jsfiddle?你不需要一个key关于财产theadtbody通过的方式。
nilgun

我在原始问题@nilgun中添加了行组件。
Brett DeWoody 2015年

3
某些商品可能没有ID或具有相同的ID?
nilgun

Answers:


620

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

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

在您的代码中,每个人<TableRowItem key={item.id} data={item} columns={columnNames}/>都试图在其中生成一些没有键的子代。

检查此示例

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

在示例中,如果我们不给钥匙<b>,而我们也希望更新object.city起反应需要重新渲染整个行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

对文档进行核对:键在对帐中的重要性:


5
我遇到了完全相同的错误。聊天后解决了吗?如果是这样,请您发布有关此问题的更新。
Deke

1
答案有效,德克。只要确保keyprop的值对于每个项目都是唯一的,然后将keyprop 放置在最接近数组边界的组件上即可。例如,在React Native中,我首先尝试将keyprop 放置在<Text>组件上。但是我不得不把它放到的<View>父组件中<Text>。如果Array == [(View> Text),(View> Text)],则需要将其放入View。不是文字。
scaryguy

237
为什么React本身很难生成唯一密钥?
达沃·卢西奇


5
这几乎是上面链接到问题聊天室中的一个笔记上的正式用词:密钥是关于集合成员的身份的,并且自动生成从任意迭代器中出现的项的密钥可能会对React产生性能影响。图书馆。
sameers

522

遍历数组时要小心!!

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

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

但是,在许多情况下不是!这是一种反模式,在某些情况下可能导致不良行为


了解key道具

React使用该key道具来了解组件与DOM元素的关系,然后将其用于对帐过程。因此,密钥始终保持唯一性非常重要,否则React很有可能会混淆元素并变异不正确的元素。同样重要的是,这些键在所有重新渲染过程中都应保持静态,以保持最佳性能。

就是说,只要知道阵列是完全静态的,就不必总是应用上述方法。但是,在可能的情况下,鼓励采用最佳实践。

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

  • 关键不在于性能,而在于身份(反过来又可以带来更好的性能)。随机分配且变化的值不是身份
  • 在不知道数据建模方式的情况下,我们无法现实地[自动]提供密钥。我建议如果您没有ID,也许使用某种哈希函数
  • 使用数组时,我们已经具有内部键,但是它们是数组中的索引。当您插入新元素时,这些键是错误的。

简而言之,a key应该是:

  • 唯一 -密钥不能与兄弟组件的密钥相同。
  • 静态 -关键不应在渲染之间更改。


使用key道具

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


不好(可能)

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

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

在此代码段中,我们使用的是非静态数组,我们并不仅限于将其用作堆栈。这是一种不安全的方法(您会明白为什么)。请注意,当我们将项目添加到数组的开头(基本上不移位)时,每个项目的值都<input>保持不变。为什么?因为key不能唯一地标识每个项目。

换句话说,首先Item 1具有key={0}。当我们添加第二个项目时,最上面的项目变成Item 2,然后Item 1是第二个项目。但是,现在Item 1已经key={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>

尽管此方法可能会保证键的唯一性,但即使不需要时,也会始终强制做出反应以重新呈现列表中的每个项目。这是一个非常糟糕的解决方案,因为它会极大地影响性能。更不用说在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调用或从异步API调用接收到),以便仅执行一次,而不是每次组件都重新渲染。已经有少数可以为您提供此类密钥的库。这是一个示例:react-key-index


1
官方文档中,它们用于toString()转换为字符串,而不是保留为数字。要记住这一点重要吗?
skube

1
@skube,不,您也可以使用整数key。不知道为什么他们要转换它。
克里斯(Chris)2007年

1
我想您可以使用整数,但是应该吗?根据他们的文档,他们声明“ ...选择键的最佳方法是使用可唯一标识... 的字符串 ”(强调我的意思)
skube

2
@skube,是的,这是完全可以接受的。如上面的示例所述,您可以使用迭代数组的项目索引(这是一个整数)。甚至文档都声明:“作为最后的手段,您可以将数组中项的索引作为键传递”。尽管如此,key总会变成字符串。
克里斯(Chris)

3
@ farmcommand2,密钥被应用于React Components,并且在兄弟姐妹之间必须唯一。如上所述。换句话说,在数组中是唯一的
克里斯,

8

这可能对某人没有帮助,但可能是快速参考。这也与上述所有答案类似。

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

return (
    {myList.map(item => (
       <>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </>
     )}
 )

经过一番尝试和错误(和一些挫折)后,将键属性添加到最外层的块即可解决该问题。另请注意,<>标记现在已被标记替换。

return (

    {myList.map((item, index) => (
       <div key={index}>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </div>
     )}
 )

当然,在上面的示例中,我一直很天真地使用迭代索引(索引)填充键值。理想情况下,您将使用列表项特有的内容。


这非常有帮助,谢谢!我什至没有意识到我必须将其放置在最外层
Charles Smith

6

警告:数组或迭代器中的每个子代都应具有唯一的“键”道具。

这是一个警告,因为要迭代的数组项将需要独特的相似之处。

React将迭代的组件渲染处理为数组。

解决此问题的更好方法是在要迭代的数组项上提供索引,例如:

class UsersState extends Component
    {
        state = {
            users: [
                {name:"shashank", age:20},
                {name:"vardan", age:30},
                {name:"somya", age:40}
            ]
        }
    render()
        {
            return(
                    <div>
                        {
                            this.state.users.map((user, index)=>{
                                return <UserState key={index} age={user.age}>{user.name}</UserState>
                            })
                        }
                    </div>
                )
        }

索引是React内置的道具。


2
如果物品以某种方式重新布置,则这种方法可能会带来危险。但是,如果它们保持静态,那就很好。
克里斯(Chris)

@chris我完全同意您的看法,因为在这种情况下,索引可能会重复。最好对键使用动态值。
Shashank Malviya

@chris我也同意你的评论。我们应该使用动态值而不是索引,因为可能存在重复项。为简单起见,我这样做了。顺便说一句感谢您的贡献(已投票)
Shashank Malviya

6

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

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

6

检查:键= undef !!!

您还会收到警告消息:

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

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

<ObjectRow key={someValue} />

someValue未定义!!!请先检查一下。您可以节省时间。


1

在react中定义唯一键的最佳解决方案:在地图内初始化名称后,然后按key = {post.id}定义键,或者在我的代码中看到我定义了名称项,然后按key = {item.id定义键}:

<div className="container">
                {posts.map(item =>(

                    <div className="card border-primary mb-3" key={item.id}>
                        <div className="card-header">{item.name}</div>
                    <div className="card-body" >
                <h4 className="card-title">{item.username}</h4>
                <p className="card-text">{item.email}</p>
                    </div>
                  </div>
                ))}
            </div>


0

这是一个警告,但是解决这个问题会使Reacts更快地渲染

这是因为React需要唯一地标识列表中的每个项目。假设如果该列表中某个元素的状态在Reacts中发生了变化,Virtual DOM那么React需要找出发生了更改的元素以及DOM中需要更改的位置,以便浏览器DOM与Reacts虚拟DOM保持同步。

作为解决方案,只需key为每个li标签引入一个属性。key对于每个元素,这应该是唯一的值。


这并不完全正确。如果添加道具,渲染将不会更快key。如果您不提供一个,React将自动分配一个(迭代的当前索引)。
克里斯(Chris

在这种情况下,@ Chris为什么会发出警告?
黄金

因为没有提供密钥,React不知道您的数据如何建模。如果修改数组,可能会导致不良结果。
克里斯(Chris

在数组修改的情况下,@Chris如果没有提供键,React将根据该值更正索引。无论如何,我认为从React消除额外的开销会对渲染过程产生一些影响。
黄金

再次,React基本上会做key={i}。因此,这取决于数组包含的数据。例如,如果您有list ["Volvo", "Tesla"],那么很显然沃尔沃用钥匙识别0特斯拉用1- 识别,因为这是它们在循环中出现的顺序。现在,如果您对数组重新排序,则将交换密钥。对于React,由于“对象” 0仍然位于顶部,它将宁愿将此更改解释为“重命名”,而不是重新排序。正确的键在这里将需要按顺序10。您不一定总是在运行时进行重新排序,但是这样做会带来风险。
克里斯(Chris

0
var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c, i) {
          return <td key={i}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

这样可以解决问题。


0

由于<></>遇到了数组中某些项的返回而null需要返回时,我遇到了此错误消息。


-1

我为每个键使用Guid修复了此问题,如下所示:

guid() {
    return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' +
        this.s4() + '-' + this.s4() + this.s4() + this.s4();
}

s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}

然后将此值分配给标记:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}

2
添加随机数作为密钥是有害的!这将防止对检测到更改做出反应,这是关键的目的。
pihentagy

-1

基本上,您不应该将数组的索引用作唯一键。相反,您可以使用a setTimeout(() => Date.now(),0)来设置唯一键或唯一uuid或任何其他将生成唯一id而不是将数组索引作为唯一id的方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.