【问题标题】:Understanding unique keys for array children in React.js了解 React.js 中数组子项的唯一键
【发布时间】:2022-01-15 21:11:38
【问题描述】:

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

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

我的TableComponent 渲染方法返回:

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

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

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

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

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

【问题讨论】:

  • JS 数组中的行应该有唯一的key 属性。它将帮助 ReactJS 找到对适当 DOM 节点的引用并仅更新标记内的内容,而不是重新渲染整个表/行。
  • 你也可以分享rows数组或者更好的jsfiddle吗?顺便说一下,theadtbody 上不需要 key 属性。
  • 我将行组件添加到原始问题@nilgun。
  • 是否有可能有些商品没有id或id相同?

标签: javascript reactjs


【解决方案1】:

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

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

在您的代码中,每个&lt;TableRowItem key={item.id} data={item} columns={columnNames}/&gt; 都试图在没有密钥的情况下渲染其中的一些子项。

检查this example

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

在示例中,如果我们没有为 &lt;b&gt; 元素提供键并且我们想更新 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

React 文档说明密钥在对账中的重要性:Keys

【讨论】:

  • 我遇到了完全相同的错误。聊天后解决了吗?如果是这样,您能否发布对此问题的更新。
  • 为什么 React 自己生成唯一密钥这么难?
  • This is pretty much the official word 在上面链接的问题聊天中所做的注释:密钥是关于集合成员的身份,并且为任意迭代器中出现的项目自动生成密钥可能具有性能React 库中的含义。
  • 是否有文档解释为什么不仅子节点需要唯一键,而且这些子节点的 children 也需要?我在文档中找不到任何具体的内容。
【解决方案2】:

遍历数组时要小心!!

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

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

但是,在许多情况下并非如此!这是反模式,在某些情况下会导致不良行为


了解key 属性

React 使用 key 属性来理解组件与 DOM 元素的关系,然后将其用于reconciliation process。因此,关键 always 保持 unique 非常重要,否则 React 很有可能会混淆元素并改变不正确的元素。为了保持最佳性能,这些键在所有重新渲染过程中保持静态也很重要。

话虽如此,只要数组是完全静态的,总是需要应用上述内容。但是,我们鼓励尽可能采用最佳做法。

一位 React 开发者在 this GitHub issue 中说:

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

简而言之,key 应该是:

  • 唯一 - 密钥不能与 sibling component 的密钥相同。
  • 静态 - 一个键在渲染之间永远不应该改变。

使用 key 属性

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


不好(可能)

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

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

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>

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

换句话说,一开始Item 1key={0}。当我们添加第二项时,顶部的项变为Item 2,然后是Item 1作为第二项。然而,现在Item 1key={1} 而不是key={0}。相反,Item 2 现在有key={0}!!

因此,React 认为 &lt;input&gt; 元素没有改变,因为带有键 0Item 始终位于顶部!

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

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

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

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

如果我们在上面的 sn-p 中将添加的项目到数组的末尾,则每个现有项目的顺序总是正确的。


非常糟糕

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

这可以说是best approach,因为它使用的属性对于数据集中的每个项目都是唯一的。例如,如果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

【讨论】:

  • official docs 中,他们使用toString() 转换为字符串而不是保留为数字。记住这一点很重要吗?
  • @skube,不,您也可以将整数用作key。不知道他们为什么要转换它。
  • 我猜你可以使用整数,但应该你呢?根据他们的文档,他们声明“...选择密钥的最佳方法是使用唯一标识...的 字符串”(强调我的)
  • @skube,是的,这是完全可以接受的。如上面示例中所述,您可以使用迭代数组的项目索引(这是一个整数)。甚至文档声明:“作为最后的手段,您可以将数组中的项目索引作为键传递”。但发生的情况是,key 总是最终成为一个字符串。
  • @farmcommand2,键应用于 React 组件,并且需要在 兄弟 中是唯一的。上面已经说明了这一点。也就是说,数组中唯一的
【解决方案3】:

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

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

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

经过一些尝试和错误(以及一些挫折),在最外面的块中添加一个关键属性解决了它。另请注意,&lt;&gt; 标记现在已替换为 &lt;div&gt; 标记。

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

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

【讨论】:

  • 这非常有帮助,谢谢!我什至没有意识到我必须把它放在最外层
  • 我正在做类似的分组,但使用表格行。您不能将表格行包装在 div 中。相反,将其包装在带有键的反应片段中,如下所示:stackoverflow.com/a/51875412/750914
  • 谢谢。这对我有帮助。我在 map() 中使用 > 删除这对我有帮助。
  • 的简短语法。所以如果你想添加一个键,你可以这样做:
  • 我在使用 Material-UI 时遇到了同样的问题,我使用的是列表(以及其他地方的菜单)并将这些组件的子项放置在工具提示中。为工具提示提供“键”而不是 ListItem/MenuItem 本身可以消除警告。
【解决方案4】:

检查:key = undef !!!

您还收到了警告消息:

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

如果你的代码是正确的,但是如果是在

<ObjectRow key={someValue} />

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      警告:数组或迭代器中的每个子元素都应该有一个唯一的“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 内置的 props。

      【讨论】:

      • 如果项目以某种方式重新排列,这种方法有潜在的危险。但如果它们保持静止,那就没问题了。
      • @chris 我完全同意你的观点,因为在这种情况下索引可能会重复。最好为键使用动态值。
      • @chris 我也同意你的评论。我们应该使用动态值而不是索引,因为可能存在重复。为了简单起见,我这样做了。顺便说一句,感谢您的贡献(赞成)
      【解决方案7】:

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

      • 该值不能与它的兄弟相同(相同)
      • 不应在渲染之间更改

      例如,键值可以是database idUUID (Universal Unique Identifier)

      这里的键是手动处理的:

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

      您也可以让 React 使用 React.Children.toArray 处理密钥

      <tbody>
        {React.Children.toArray(rows.map((row) => <ObjectRow />))}
      </tbody>
      

      【讨论】:

        【解决方案8】:

        在 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

        【讨论】:

          【解决方案9】:

          当您没有用于渲染项目的稳定 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

          【讨论】:

            【解决方案10】:

            react 中定义唯一键的最佳解决方案: 在地图中,您初始化名称 post 然后通过 key={post.id} 定义键,或者在我的代码中您看到我定义了名称 item 然后我通过 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>

            【讨论】:

              【解决方案11】:

              我遇到此错误消息是因为数组中的某些项目返回了&lt;&gt;&lt;/&gt;,而需要返回null

              【讨论】:

                【解决方案12】:

                我有一个唯一的密钥,只需像这样将它作为道具传递:

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

                这个页面很有帮助:

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

                【讨论】:

                  【解决方案13】:

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

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

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

                  【讨论】:

                  • 这并不完全正确。如果添加 key 属性,渲染将不会更快。如果你不提供一个,React 会自动分配一个(迭代的当前索引)。
                  • @Chris 在这种情况下为什么会发出警告?
                  • 因为没有提供密钥,React 不知道你的数据是如何建模的。如果数组被修改,这可能会导致不希望的结果。
                  • @Chris 在数组修改的情况下,如果我们没有提供键,React 将根据该索引更正索引。无论如何,我认为从 React 中移除额外的开销会对渲染过程产生一些影响。
                  • 再一次,React 基本上会做key={i}。所以这取决于你的数组包含的数据。例如,如果您有["Volvo", "Tesla"] 列表,那么显然沃尔沃通过密钥0 和特斯拉通过1识别 - 因为这是它们将出现在循环中的顺序.现在,如果您对数组重新排序,则交换键。对于 React,由于“对象”0 仍然在顶部,它宁愿将此更改解释为“重命名”,而不是重新排序。这里的正确键需要依次是1,然后是0。您并不总是在运行时重新排序,但这样做时会带来风险。
                  【解决方案14】:
                  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】:

                    在我的例子中,将 id 设置为 tag

                    <tbody key={i}>
                    

                    问题解决了。

                    【讨论】:

                      【解决方案16】:
                      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`
                      

                      【讨论】:

                        【解决方案17】:

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

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

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

                        【讨论】:

                        • ` rowKey={(item) => item.id}` table 标签内的属性解决了我的问题
                        • 这很可能是 浏览器缓存 的问题。您可以在同一本地主机上hard-refresh 删除任何存储的缓存。
                        【解决方案18】:

                        这是一个简单的例子,我首先使用了 &amp;&amp; 的反应条件然后映射,在我添加了 key 用户 ID 以确保它是唯一的

                         <tbody>
                                                            {users &&
                                                            users.map((user) => {
                                                                return <tr key={user._id}>
                                                                    <td>{user.username}</td>
                                                                    <td><input
                                                                        name="isGoing"
                                                                        type="checkbox"
                                                                        checked={user.isEnabled}
                                                                        onChange={handleInputChangeNew}/></td>
                                                                    <td>{user.role.roleTitle} - {user.role.department.departmentName}</td>
                                                                    {/*<td className="text-right">
                                                                            <Button>
                                                                                ACTION
                                                                            </Button>
                                                                        </td>*/}
                                                                </tr>
                                                            })
                                                            }
                        
                        
                                                            </tbody>
                        

                        【讨论】:

                          【解决方案19】:

                          我不去详细解释,但这个答案的关键是“关键” 只需将 key 属性放在标签中,并确保每次迭代都赋予它独特的价值

                          #确保键的值不与其他值冲突

                          例子

                          <div>
                                  {conversation.map(item => (
                                    <div key={item.id  } id={item.id}>
                                    </div>
                                  ))}
                                </div>
                          

                          其中的对话是一个数组,如下所示:

                            const conversation = [{id:"unique"+0,label:"OPEN"},{id:"unique"+1,label:"RESOLVED"},{id:"unique"+2,label:"ARCHIVED"},
                             ]
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2020-11-15
                            • 2016-11-18
                            • 2014-11-26
                            • 2011-12-13
                            相关资源
                            最近更新 更多