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

Bre*_*ody 549 javascript reactjs

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

数组中的每个子节点都应该具有唯一的"键"支柱.
检查TableComponent的render方法.

我的TableComponentrender方法返回:

<table>
  <thead key="thead">
    <TableHeader columns={columnNames}/>
  </thead>
  <tbody key="tbody">
    { rows }
  </tbody>
</table>
Run Code Online (Sandbox Code Playgroud)

TableHeader组件是单行,并且还具有分配给它的唯一键.

每个rowin rows都是使用具有唯一键的组件构建的:

<TableRowItem key={item.id} data={item} columns={columnNames}/>
Run Code Online (Sandbox Code Playgroud)

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>
    )
  }
});
Run Code Online (Sandbox Code Playgroud)

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

jmi*_*gov 530

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

这样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);
Run Code Online (Sandbox Code Playgroud)

@Chris在底部发布的答案比这个答案更详细.请查看/sf/answers/3072503381/

关于密钥在协调中的重要性的反馈文档:密钥

  • 为什么React难以自行生成唯一密钥? (181认同)
  • @DavorLucic,这是一个讨论:https://github.com/facebook/react/issues/1342#issuecomment-39230939 (11认同)
  • 是否有文档解释为什么不仅子节点需要唯一键,而且这些子节点的 _children_ 也需要?我在文档中找不到任何与此相关的内容。 (7认同)
  • 我遇到了完全相同的错误.聊天后这个问题解决了吗?如果是这样,请您发布此问题的更新. (5认同)
  • [这是相当多的官方消息](https://facebook.github.io/react/docs/lists-and-keys.html#keys)对这个问题做了说明聊天连接到上面:键是关于身份一个集合的成员和从任意迭代器中出现的项目的自动生成键可能在React库中具有性能影响. (4认同)

Chr*_*ris 414

迭代数组时要小心!

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

Each child in an array should have a unique "key" prop.
Run Code Online (Sandbox Code Playgroud)

但是,在许多情况下它不是!这种反模式某些情况下会导致不必要的行为.


理解key道具

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

话虽这么说,一个并不总是需要应用上面,只要它是众所周知的是,数组是完全静态的.但是,尽可能鼓励应用最佳实践.

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

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

简而言之,key应该是:

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


使用key道具

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


坏(可能)

<tbody>
    {rows.map((row, i) => {
        return <ObjectRow key={i} />;
    })}
</tbody>
Run Code Online (Sandbox Code Playgroud)

这可以说是在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"));
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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

换句话说,起初Item 1key={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>
Run Code Online (Sandbox Code Playgroud)

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

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


很好

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uniqueId} />;
    })}
</tbody>
Run Code Online (Sandbox Code Playgroud)

这可以说是最好的方法,因为它使用了数据集中每个项目唯一的属性.例如,如果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>
Run Code Online (Sandbox Code Playgroud)

这也是一个很好的方法.如果您的数据集不包含任何保证唯一性的数据(例如,任意数字的数组),则可能存在键冲突.在这种情况下,最好在迭代数据集之前为数据集中的每个项目手动生成唯一标识符.优选地,当安装组件时或者当接收数据集时(例如,props来自异步API调用或来自异步API调用),为了仅执行一次,而不是每次重新呈现组件.已经有一些库可以为您提供这样的密钥.这是一个例子:react-key-index.

  • 在[官方文档](https://facebook.github.io/react/docs/lists-and-keys.html#keys)中,他们使用`toString()`转换为字符串而不是保留为数字。记住这一点很重要吗? (3认同)
  • @skube,不,您也可以使用整数作为“key”。不知道他们为什么要转换它。 (3认同)
  • @skube,是的,这是完全可以接受的。如上面的示例所述,您可以使用迭代数组的项目索引(这是一个整数)。甚至文档声明:*“作为最后的手段,您可以将数组中项目的索引作为键传递”*。然而,无论如何,`key` 总是最终成为一个字符串。 (2认同)
  • 该键应该仅在该数组中唯一还是应该在整个应用程序中唯一? (2认同)
  • @ farmcommand2,密钥应用于React组件,并且在**兄弟姐妹之间必须唯一。如上所述。换句话说,在数组中是唯一的 (2认同)

api*_*ang 35

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

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

return (
    {myList.map(item => (
       <>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </>
     )}
 )
         
Run Code Online (Sandbox Code Playgroud)

经过一些试验和错误(以及一些挫折),向最外层块添加一个关键属性解决了它。另请注意,<>现在标签已替换为<div>标签。

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

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

  • &lt;&gt; 是 &lt;React.Fragment&gt; 的简短语法。因此,如果你想添加一个键,你可以这样做: &lt;React.Fragment key={item.id}&gt; (3认同)
  • 这非常有帮助,谢谢!我什至没有意识到我必须把它放在最外层 (2认同)

小智 11

这里是 React 文档,很好地解释了如何使用 Key 属性,键应该在父组件中定义,而不应该在子组件中使用。反应文档

在此输入图像描述

在此输入图像描述


Ger*_*erd 10

检查:key = undef !!!

您还收到警告消息:

Each child in a list should have a unique "key" prop.
Run Code Online (Sandbox Code Playgroud)

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

<ObjectRow key={someValue} />
Run Code Online (Sandbox Code Playgroud)

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


Foy*_*yez 8

tbody您应该为其中的每个子键使用唯一的值

  • 该值不能与其同级值相同(相同)
  • 渲染之间不应改变

例如,键值可以是数据库 idUUID(通用唯一标识符)

这里的按键是手动处理的:

<tbody>
  {rows.map((row) => <ObjectRow key={row.uuid} />)}
</tbody>
Run Code Online (Sandbox Code Playgroud)

您还可以使用React.Children.toArray让 React 处理键

<tbody>
  {React.Children.toArray(rows.map((row) => <ObjectRow />))}
</tbody>
Run Code Online (Sandbox Code Playgroud)


Sha*_*iya 5

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

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

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>
                )
        }
Run Code Online (Sandbox Code Playgroud)

索引是React内置的道具。

  • 如果以某种方式重新安排了项目,则此方法可能存在危险。但是,如果它们保持静态,那就很好。 (2认同)

Mah*_*our 5

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

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})
Run Code Online (Sandbox Code Playgroud)


Nas*_*tad 5

当您没有\xe2\x80\x99t 渲染项目的稳定 ID 时,您可以使用项目索引作为键作为最后的手段:

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

请参考列表和按键 - React

\n