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

时间:2015-02-04 19:06:12

标签: javascript reactjs

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

  

数组中的每个子节点都应该有一个唯一的键#34;道具。
  检查TableComponent的render方法。

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

导致唯一关键道具错误的原因是什么?

19 个答案:

答案 0 :(得分:441)

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

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

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

检查this example

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

在示例中,如果我们没有为<b>元素提供密钥,并且我们只想更新 object.city,则React需要重新渲染整行而不仅仅是元素。

以下是代码:

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

在对帐中反映密钥重要性的文档:Keys

答案 1 :(得分:342)

迭代数组时要小心!!

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

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

然而,在许多情况下它不是!这是反模式,可能会在某些情况下导致不受欢迎的行为


了解key道具

React使用key prop来理解组件到DOM元素的关系,然后将其用于reconciliation process。因此,密钥总是保持唯一是非常重要的,否则React很有可能会混淆元素并改变不正确的元素。在所有重新渲染中,这些键保持静态也很重要,以保持最佳性能。

话虽如此,如果已知该数组是完全静态的,那么总是需要应用上述内容。但是,尽可能鼓励采用最佳实践。

React开发人员在this GitHub issue中说:

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

简而言之,key应为:

  • 唯一 - 密钥不能与sibling component的密钥相同。
  • 静态 - 在呈现之间不应更改密钥。


使用key道具

根据上述说明,请仔细研究以下样本,并尽可能尝试实施推荐的方法。

坏(可能)

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

这可以说是在React中迭代数组时最常见的错误。这种方法在技术上&#34;错误&#34; ,它只是...... &#34;危险&#34; 如果你不知道你在做什么。如果您正在迭代静态数组,那么这是一种非常有效的方法(例如导航菜单中的链接数组)。但是,如果要添加,删除,重新排序或过滤项目,则需要小心。在官方文档中查看此detailed explanation

&#13;
&#13;
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"));
&#13;
<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>
&#13;
&#13;
&#13;

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

换句话说,首先Item 1key={0}。当我们添加第二个项目时,顶部项目变为Item 2,然后Item 1作为第二个项目。但是,现在Item 1已经key={1}而不是key={0}了。相反,Item 2现在有key={0} !!

因此,React认为<input>元素没有改变,因为带有Item的{​​{1}}始终位于顶部!

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

如果以某种方式过滤,重新排列或添加/删除项目,则此方法存在风险。如果它总是静止的,那么使用起来非常安全。例如,像0这样的导航菜单可以安全地使用此方法进行迭代,因为您可能永远不会添加新链接或重新排列它们。

简而言之,您可以安全将索引用作["Home", "Products", "Contact us"]

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

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

非常糟糕

key

虽然这种方法可能会保证密钥的唯一性,但是总是强制响应重新呈现列表中的每个项目,即使这不是必需的。这是一个非常糟糕的解决方案,因为它会极大地影更不用说在<tbody> {rows.map((row) => { return <ObjectRow key={Math.random()} />; })} </tbody> 产生两次相同数字的情况下,不能排除发生密钥冲突的可能性。

  

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

非常好

Math.random()

这可以说是best approach因为它使用了对数据集中每个项目唯一的属性。例如,如果<tbody> {rows.map((row) => { return <ObjectRow key={row.uniqueId} />; })} </tbody> 包含从数据库获取的数据,则可以使用表的主键(,通常是自动递增的数字)。

  

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

rows

这也是一个很好的方法。如果您的数据集不包含任何保证唯一性的数据(,例如,任意数字的数组),则可能存在密钥冲突。在这种情况下,最好在迭代数据集之前为数据集中的每个项目手动生成唯一标识符。优选地,在安装组件或收到数据集时(例如来自componentWillMount() { let rows = this.props.rows.map(item => { return {uid: SomeLibrary.generateUniqueID(), value: item}; }); } ... <tbody> {rows.map((row) => { return <ObjectRow key={row.uid} />; })} </tbody> 或来自异步API调用),为了仅执行一次,不是每次组件重新渲染。已经有一些库可以为您提供这样的密钥。以下是一个示例:react-key-index

答案 2 :(得分:16)

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

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

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

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

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

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

答案 3 :(得分:3)

警告:数组或迭代器中的每个子节点都应该有一个唯一的“键”支柱。

这是一个警告,因为我们要迭代的数组项需要一个独特的相似性。

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内置道具。

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

我找到的最简单的解决方案是为列表中的每个元素创建唯一的ID。有2个库,uniqid和uuid。

npm install uniqid

然后在您的代码中:

import uniqueid from 'uniqid'
- OR if you use require:   var uniqid = require('uniqid');

然后只需将一个键添加到需要它的HTML元素中。在以下示例中,请注意 key = {uniqueid()}

{this.state.someList.map((item) => <li>{item}</li>)}
I changed to this:
{this.state.someList.map((item) => <li key={uniqueid()}>{item}</li>)}

答案 6 :(得分:2)

在ReactJS中,如果要渲染元素数组,则每个元素都应具有唯一的键。通常,这些情况会创建一个列表。

示例:

function List() {
  const numbers = [0,1,2,3];
 
  return (
    <ul>{numbers.map((n) => <li> {n} </li>)}</ul>
  );
}

 ReactDOM.render(
  <List />,
  document.getElementById('root')
);

在上面的示例中,它使用li标签创建了一个动态列表,因此由于li标签没有唯一键,因此会显示错误。

修复后:

function List() {
  const numbers = [0,1,2,3];
 
  return (
    <ul>{numbers.map((n) => <li key={n}> {n} </li>)}</ul>
  );
}

 ReactDOM.render(
  <List />,
  document.getElementById('root')
);

在没有唯一键的情况下使用map的替代解决方案(react eslint不建议这样做):

function List() {
  const numbers = [0,1,2,3,4,4];
 
  return (
    <ul>{numbers.map((n,i) => <li key={i}> {n} </li>)}</ul>
  );
}

 ReactDOM.render(
  <List />,
  document.getElementById('root')
);

实时示例:https://codepen.io/spmsupun/pen/wvWdGwG

答案 7 :(得分: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>

答案 8 :(得分:1)

您应该为 tbody where

的每个子键使用唯一值
  • 该值不能与其同级相同(相同)
  • 不应在渲染之间改变

例如,键值可以是数据库 IDUUID (Universal Unique Identifier)

这里的键是手动处理的:

<tbody>
  {rows.map((row) => <ObjectRow key={row.uuid} />)}
</tbody>

您也可以使用 React.Children.toArray

让 React 处理密钥
<tbody>
  {React.Children.toArray(rows.map((row) => <ObjectRow />))}
</tbody>

答案 9 :(得分:1)

If you are getting error like :

> index.js:1 Warning: Each child in a list should have a unique "key" prop.

Check the render method of `Home`. See https://reactjs.org/link/warning-keys for more information.

Then Use inside map function like:

  {classes.map((user, index) => (
              <Card  **key={user.id}**></Card>
  ))}`enter code here`

答案 10 :(得分:1)

就我而言,将 id 设置为标记

<tbody key={i}>

问题解决了。

答案 11 :(得分:1)

当您没有用于呈现项目的稳定 ID 时,您可以将项目索引用作键作为最后的手段:

const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
   <li key={index}>
      {todo.text}
   </li>
);

请参考List and Keys - React

答案 12 :(得分:1)

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

答案 13 :(得分:0)

我有一个唯一的密钥,只需要像这样通过它来作为道具:

<CompName key={msg._id} message={msg} />

此页面很有帮助:

https://reactjs.org/docs/lists-and-keys.html#keys

答案 14 :(得分: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>
    )
  }
});

这将解决问题。

答案 15 :(得分:0)

检查:key = undef !!!

您还会收到警告消息:

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

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

<ObjectRow key={someValue} />

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

答案 16 :(得分:0)

我使用Guid为每个键修复此问题,如下所示: 生成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}
              />
          ))}

答案 17 :(得分:0)

我遇到了类似的问题,但并不准确。尝试了所有可能的解决方案,但无法摆脱该错误

<块引用>

数组中的每个孩子都应该有一个唯一的“key”属性。

然后我尝试在不同的本地主机中打开它。我不知道如何,但它奏效了!

答案 18 :(得分:-1)

这是一个警告,但是 解决此问题将使Reacts渲染速度更快

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

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