ChatGPT解决这个技术问题 Extra ChatGPT

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

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

数组中的每个孩子都应该有一个唯一的“关键”道具。检查 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?

L
Liam

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

这种方式 React 可以处理最小的 DOM 更改。

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

检查this example

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

在示例中,如果我们没有为 <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 在底部发布的答案比这个答案更详细。

关于密钥在协调中的重要性的 React 文档:Keys


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

遍历数组时要小心!

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

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

但是,在很多情况下并非如此!这是一种反模式,在某些情况下会导致不需要的行为。

了解关键道具

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

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

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

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

简而言之,key 应该是:

唯一 - 键不能与同级组件的键相同。

静态 - 一个键不应该在渲染之间改变。

使用 key 道具

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

不好(可能)

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

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

类 MyApp 扩展 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(

    {this.state.arr.map( (item, i) => {item + " "} )}
); } } const Item = (props) => { return (
  • ); } ReactDOM.render(, document.getElementById("app"));

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

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

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

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

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

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

    数组是静态的,永远不会改变。

    数组永远不会被过滤(显示数组的子集)。

    数组永远不会重新排序。

    该数组用作堆栈或 LIFO(后进先出)。换句话说,添加只能在数组的末尾进行(即push),并且只能删除最后一项(即pop)。

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

    很坏

    <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 组件,并且需要在兄弟姐妹中是唯一的。上面已经说明了这一点。换句话说,在数组中是唯一的
    P
    Penny Liu

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

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

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

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

    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 本身消除了警告。
    G
    Gerd

    检查:key = undef !!!

    您还收到警告消息:

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

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

    <ObjectRow key={someValue} />
    

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


    字面上得救了!
    M
    Mahdi Bashirpour

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

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

    F
    Foyez

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

    该值不能与其兄弟相同(相同)

    不应该在渲染之间改变

    例如,键值可以是 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>
    

    S
    Stephen Rauch

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


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

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


    D
    Dharman

    在 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


    哇,tnx 的简单解决方案! key={i} 对我有用
    K
    Khadim H.

    在反应中定义唯一键的最佳解决方案:在地图中您初始化名称 post 然后键定义 key={post.id} 或在我的代码中您看到我定义名称项目然后我定义键 key={item.id }:

    {posts.map(item =>(
    {item .name}

    {item.username}

    {item.email }

    ))}


    F
    Felipe

    我遇到了这个错误消息,因为当需要返回 null 时,数组中的某些项目会返回 <></>


    A
    Arvind

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

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

    此页面很有帮助:

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


    s
    superup

    就我而言,将 id 设置为 tag

    <tbody key={i}>
    

    问题已经解决了。


    p
    prime

    这是一个警告,但解决这个问题将使 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"],那么很明显,Volvo 由键 0识别,而 Tesla 具有 1 - 因为这是它们将出现在循环中的顺序.现在,如果您对数组重新排序,则交换键。对于 React,由于“对象”0 仍然在顶部,它宁愿将此更改解释为“重命名”,而不是重新排序。这里正确的键需要依次是 1 然后是 0。您并不总是在运行时重新排序,但是当您这样做时,这是一种风险。
    R
    Ramesh
    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>
        )
      }
    });
    

    这将解决问题。


    V
    Vik2696
    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`
    

    L
    L3xpert

    这是一个简单的例子,我首先使用了 && 的反应条件然后映射,在我添加了 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>
    

    N
    Niloy dey
    if we have array object data . then we are map for showing  the data . and pass the unique id (key = {product.id} ) because browser can selected the unique data 
    
    example : [
        {
            "id": "1",
            "name": "walton glass door",
            "suplier": "walton group",
            "price": "50000",
            "quantity": "25",
            "description":"Walton Refrigerator is the Best Refrigerator brand in bv 
             Bangladesh "
        },
        {
            
            "id": "2",
            "name": "walton glass door",
            "suplier": "walton group",
            "price": "40000",
            "quantity": "5",
            "description":"Walton Refrigerator is the Best Refrigerator brand in 
             Bangladesh "
        },
    }
    
    
    now we are maping the data and pass the unique id: 
    {
        products.map(product => <product product={product} key={product.id} 
        </product>)
    }
    

    B
    Basurp Patil

    以下是使用 Key 属性很好解释的 React 文档,键应在父组件中定义,不应在子组件中使用。React Docs

    https://i.stack.imgur.com/B0UWZ.png

    https://i.stack.imgur.com/WbMzO.png


    虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
    D
    Dila Gurung

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

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

    例子

    <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"},
       ]
    

    A
    Atish Barua

    你的密钥应该是唯一的。就像一个唯一的 id。你的代码应该是这样的

    <div>
     {products.map(product => (
       <Product key={product.id}>
        </Product>
        ))}
      </div>
    

    A
    A K M Intisar Islam

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

    数组中的每个孩子都应该有一个唯一的“关键”道具。

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


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

    如果您正在为这个错误而苦苦挣扎,列表中的每个孩子都应该有一个唯一的“关键”道具。

    通过将索引值声明给呈现元素内的键属性来解决。

    App.js 组件

    import Map1 from './Map1';
    
    const arr = [1,2,3,4,5];
    
    const App = () => {
      return (
        <>
         
         <Map1 numb={arr} />     
    
        </>
      )
    }
    
    export default App
    

    Map.js 组件

    const Map1 = (props) => {
    
        let itemTwo = props.numb;
        let itemlist = itemTwo.map((item,index) => <li key={index}>{item}</li>)
    
        return (
            <>        
            <ul>
                <li style={liStyle}>{itemlist}</li>
            </ul>
            </>
        )
    }
    
    export default Map1