在React中,即使已经设置了key,仍会收到关键属性警告。

45

问题

我收到了这个警告:

警告:在数组或迭代器中的每个子元素都应该有一个唯一的“key”属性。请检查EventsTable的渲染方法。有关更多信息,请参见fb.me/react-warning-keys

react-runtime-dev.js?8fefd85d334323f8baa58410bac59b2a7f426ea7:21998 警告:在数组或迭代器中的每个子元素都应该有一个唯一的“key”属性。请检查事件的渲染方法。有关更多信息,请参见fb.me/react-warning-keys

源代码

这是EventsTable

EventsTable = React.createClass({
  displayName: 'EventsTable',

  render() {
    console.dir(this.props.list);

    return (
      <table className="events-table">
        <thead>
          <tr>
            {_.keys(this.props.list[0]).map(function (key) {
              if (key !== 'attributes') {
                return <th>{key}</th>;
              }
            })}
         </tr>
        </thead>

        <tbody>
          {this.props.list.map(function (row) {
            return (
              <Event key={row.WhatId} data={row} />
            );
          })}
        </tbody>
      </table>
    )
  }
});

这是事件(Event)

Event = React.createClass({
  displayName: 'Event',

  render() {
    return (
      <tr>
        {_.keys(this.props.data).map((x) => {
          if (x !== 'attributes')
            return <td>{this.props.data[x]}</td>;
        })}
      </tr>
    )
  }
});

问题

很明显我在 <Event /> 组件上设置了 key 属性。而且我遵循的约定是应该在组件上包含 key,而不是在 HTML 标签(换句话说,在 Event 组件内部的 HTML 标签)上设置 key。根据官方 React 文档:

key 应该直接提供给数组中的组件,而不是每个组件的容器 HTML 子元素中:

我非常困惑。为什么会收到警告?

6个回答

37

当我意识到我有一个需要唯一 key 的 <React.Fragment> 时,我最终解决了这个问题。


9
我也想补充一下。我使用了 <></> 而不是 React.Fragment。我把 <></> 替换成了 <React.Fragment key={yourKeyHere}></React.Fragment>,现在一切都可以正常工作了,没有警告。 - Christopher Adams
这是我的问题 - 尝试了@ChrisAdams的解决方案,它起作用了。谢谢!我永远不会发现这个问题。 - BWeb303
使用<></>这种方式并不好,因为如果你使用<Fragment>,它会向你显示错误信息。 - Shivam
我的天啊,这太荒谬了。谢谢! - Elyse Dawson
有点冷门,但我遇到了一个非常类似的问题,当我将键(key)属性移到组件的最外层元素时,我成功解决了它。似乎React需要在那里才能识别它。 - jaggerabney

32

你有尝试给 <th> 标签添加一个 key 属性吗?

         <tr>
            {_.keys(this.props.list[0]).map(function (key) {
              if (key !== 'attributes') {
                return <th key={key}>{key}</th>;
              }
            })}
         </tr>

5
但是React文档不是说不要在HTML标签上使用key吗?请参见此处 - ffxsam
1
我的理解一直是,如果您正在迭代生成HTML的组件,则应该在组件上设置键。但在您的情况下,您并未使用组件来生成th标签,因此在这种情况下可能是有效的。 - sma
我认为你可以把key留在Event组件上,不是吗?你试过了吗? - sma
啊,明白了。这很奇怪。看起来有点过度设计,但如果您也将<th>标签作为组件并在其上放置key会怎样呢? - sma
1
将键放在<td>上是可行的。虽然不是最理想的,但我随时可以重构。你的答案绝对有帮助,谢谢! - ffxsam
显示剩余2条评论

19

简而言之

每次呈现列表(使用 map),请为列表元素(从 map 的回调返回的最顶层或“根”元素)添加一个唯一的 key 属性:

render() {
  return (
    <div>
      {this.props.data.map( element => {
         // Place the key on to the returned "root" element.
         // Also, in real life this should be a separate component...
         return <div key={element.id}>
           <span>Id (Name): </span>
           <span>{element.id} </span>
           <span>({element.name})</span>
         </div>;
      })}
    </div>
  )
}

说明

理解React中的键

官方的列表和键文档展示了如何处理列表,而相关的协调文档则解释了为什么要这样做。

当React重新渲染组件时,它会运行一个差异算法来查找新版本和旧版本列表之间的变化。比较并不总是简单的,但如果每个元素都有唯一的键,就可以清楚地确定发生了什么变化。请参见文档中的示例

<!-- previous -->
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<!-- new -->    
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

很明显,一个带有键值2014的新元素被添加了,因为我们拥有其他所有键值,并且它们没有改变。如果没有这些键值,这将是晦涩难懂的。
选择适当的键值:
现在很容易看出:
- 为什么重要的是键值应该在列表中唯一,但只在兄弟节点之间唯一,因为比较仅在给定列表的上一个和新元素之间进行。 - 相同元素之间键值应该保持不变,否则我们将比较不同的元素,无法跟踪更改。这就是为什么建议使用资源的ID或(如果没有)某些唯一于元素的数据,以及为什么不应该使用像Math.random()这样的东西。
放置组件的key属性:
key属性放置到组件中更多的是一个良好的实践约定,因为当您迭代列表并想要呈现一个元素时,这清楚地表明您应该将该代码组织到单独的组件中。
在循环中设置key属性:
您从文档中引用的语句:

键值应始终直接提供给数组中的组件,而不是提供给数组中每个组件的容器HTML子元素:

意味着如果您在循环中呈现组件,则应设置循环中组件的key属性,就像您在EventsTable组件中所做的那样。
{this.props.list.map(function (row) {
  return (
    <Event key={row.WhatId} data={row} />
  );
})}

错误的方法是将其传递给组件,然后组件会在自身上设置“key”:

代码如下:

Event = React.createClass({
  displayName: 'Event',
  render() {
    // Don't do this!
    return (
      <tr key={this.props.data.WhatId}>
        {_.keys(this.props.data).map((x) => {

这里有另一个很好的例子,关于使用React实现动态子组件,可以在这篇文章中找到。


12

检查传递给key的变量是否已定义,因为如果它是undefined,那么错误将相同,但似乎代码应该可以工作。


没错!当使用例如 someId 时,很容易忽略真实值为例如 someID 的情况。更奇怪的是,PropTypes 也没有对此进行“注释”。 - Juha Untinen

3

我也遇到了这个问题,在按照链接的方法进行修复后,问题得以解决。

例如:

{_data.map(function(object, i){
     return <div className={"row"} key={i}> 
           {[ object.name ,
      <b className="fosfo" key={i}> {object.city} </b> , // remove the key
          object.age
      ]}
</div>; 
})}

0

最简单的解决方法是为您正在映射的项目创建一个单独的组件,并将键添加到该组件中。

在现有组件上方创建一个新组件(或链接到您的调用中)。

const TableDataComponent = ({ k }) => {
   return (
     <th>{k}</th>
   )
}

然后在你的代码中使用你的 key 添加该组件:

<tr>
    {arr.map((k) => {
      return <TableDataComponent key={k._id} k={k} />
    })}
</tr>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接