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

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

我的TableComponent渲染方法返回:

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

TableHeader组件是单行,也有一个唯一的键赋给它。

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

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

是什么导致唯一键道具错误?


当前回答

在使用Mongo数据的情况下,您可以使用_id属性

arr.map((cell) =>
  <li key={cell._id}>{cell}
  </li>
);

其他回答

在迭代数组时要小心!!

这是一个常见的误解,使用数组中元素的下标是一种可以接受的抑制错误的方法,你可能熟悉:

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

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


理解关键道具

React使用关键道具来理解组件到dom元素的关系,然后将其用于协调过程。因此,关键字始终保持唯一是非常重要的,否则React很可能会混淆元素并变异出不正确的元素。同样重要的是,这些键在所有重渲染中保持静态,以保持最佳性能。

话虽如此,只要已知数组是完全静态的,就不总是需要应用上述方法。但是,只要可能,就鼓励应用最佳实践。

一个React开发者在GitHub上说:

密钥其实与性能无关,它更关乎身份(这反过来又会带来更好的性能)。随机分配和变化的值不是身份 在不知道你的数据是如何建模的情况下,我们无法(自动)提供密钥。如果你没有id,我建议你使用一些哈希函数 当我们使用数组时,我们已经有了内部键,但它们是数组的索引。当您插入一个新元素时,这些键是错误的。

简而言之,关键应该是:

唯一——键不能与同级组件的键相同。 静态-一个键不应该在渲染之间改变。


使用关键道具

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


坏(可能)

<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>

在这个代码片段中,我们使用了一个非静态数组,并且我们没有限制自己将其用作堆栈。这是一种不安全的方法(您将看到原因)。请注意,当我们向数组的开头添加项时(基本上是unshift),每个<input>的值保持不变。为什么?因为键不能唯一地标识每一项。

换句话说,一开始Item 1的key={0}。当我们添加第二个项目时,最上面的项目成为项目2,然后是项目1作为第二个项目。但是,现在Item 1的key={1}不再是key={0}了。相反,项目2现在有key={0}!!

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

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

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

简而言之,当你可以安全地使用索引作为键时:

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

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


非常糟糕的

<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>

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

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


Good

componentWillMount() {
  let rows = this.props.rows.map(item => { 
    return {uid: SomeLibrary.generateUniqueID(), value: item};
  });
}

...

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

This is also a good approach. If your dataset does not contain any data that guarantees uniqueness (e.g. an array of arbitrary numbers), there is a chance of a key collision. In such cases, it is best to manually generate a unique identifier for each item in the dataset before iterating over it. Preferably when mounting the component or when the dataset is received (e.g. from props or from an async API call), in order to do this only once, and not each time the component re-renders. There are already a handful of libraries out there that can provide you such keys. Here is one example: react-key-index.

使用UUID lib创建一个随机UUID: 执行NPM install uuid命令

import { v4 as uuid } from "uuid";

array.map((item) =>
  <li key={uuid()}>{item}
  </li>
);

只需将唯一键添加到组件

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

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

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

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

index是React内置的道具。

在使用Mongo数据的情况下,您可以使用_id属性

arr.map((cell) =>
  <li key={cell._id}>{cell}
  </li>
);