繁体   English   中英

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

[英]Understanding unique keys for array children in React.js

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

数组中的每个孩子都应该有一个独特的“关键”道具。
检查 TableComponent 的 render 方法。

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

是什么导致了唯一的密钥道具错误?

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

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

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

检查这个例子

尝试从 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

遍历数组时要小心!

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

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

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


了解key道具

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

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

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

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

简而言之,一个key应该是:

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

使用key道具

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


不好(可能)

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

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

换句话说,首先Item 1key={0} 当我们添加第二个项目时,最上面的项目变成了Item 2 ,然后是Item 1作为第二个项目。 但是,现在Item 1key={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>

虽然这种方法可能会保证键的唯一性,但它总是会强制 react 重新渲染列表中的每个项目,即使这不是必需的。 这是一个非常糟糕的解决方案,因为它极大地影响了性能。 更不用说,如果Math.random()两次产生相同的数字,则不能排除密钥冲突的可能性。

不稳定的键(如Math.random()产生的键)会导致许多组件实例和 DOM 节点不必要地重新创建,这可能会导致性能下降和子组件中的状态丢失。


很好

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

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

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

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

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)来填充键值。 理想情况下,您会使用列表项独有的东西。

检查:key = undef !!!

您还收到警告消息:

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

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

<ObjectRow key={someValue} />

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

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

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

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

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

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

这里的键是手动处理的:

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

你也可以让 React 使用React.Children.toArray处理键

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

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

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

在 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

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

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

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

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

此页面很有帮助:

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

就我而言,将 id 设置为 tag

<tbody key={i}>

问题已经解决了。

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

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

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

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

这将解决问题。

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`

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

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

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

在此处输入图像描述

在此处输入图像描述

由于React 文档,每一行/项目都应该有一个唯一的键。

键帮助 React 识别哪些项目已更改、添加或删除。

就个人而言,我更喜欢使用加密接口来生成随机 UUID:
crypto内置在 vanilla-js 中)

const numbers = [1, 2, 3, 4, 5];

const listItems = numbers.map((number) =>
  <li key={crypto.randomUUID()}>item {number}
  </li>
);

使用 uuid 库创建一个随机 UUID:
运行npm install uuid

import { v4 as uuid } from "uuid";

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

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

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

例子

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

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

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

视觉解释。

  1. 不正确的方式 key=index (数组)

在此处输入图像描述

如您所见,label 3、label 2 和 label 1全部重新渲染(在元素面板中闪烁)。

  1. 正确的方式key=uniqueId 在此处输入图像描述

只有顶部的新元素会闪烁(重新渲染)。

我认为在使用表(或类似示例)时,为了可重用性,创建一个唯一键应该从父组件传递给子组件。

因为如果你正在创建一个表,那意味着你正在从父级传递数据。 如果您分配key={row.name}可能当前数据具有name属性,但如果您想在其他地方使用此表组件,您假设在您传递的每一行数据中,您都具有name属性。

由于工程师将在父组件中准备数据,因此工程师应根据数据创建密钥 function。

const keyFunc = (student) => {
    return student.id;
  }; 

在这种情况下,工程师知道它正在发送什么数据,它知道每一行都有唯一的 id 属性。 也许在不同的数据集中,数据集是股票价格,它没有“id”属性,而是“symbol”

 const keyFunc = (stock) => {
        return stock.symbol;
      }; 

这个keyFunc应该作为 prop 传递给子组件

“列表中的每个孩子都应该有一个独特的‘关键’道具。” 当您创建没有特殊 key 属性的元素列表时,React 中会出现警告。 必须将键分配给循环中的每个元素,以便为 React 中的元素提供稳定的标识。

我们可以将 object 的 id 属性设置为唯一键。

export default function App() {
      const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
 ];
 return (
<div>
    <ul>
        {posts.map(value => 
            <li key={value.id}>{value.title}</li>
        )}
    </ul>
</div>
 );}


//simple way

//if u using ant design remove the empty fragment...

//worng ans---> change to following crt ans

export default function App() {
      const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
 ];
 {fields.map((field,index)=>{
return(
  <> //empty fragment
   <Row key={index}>
    <Col span={6}>hello</Col>
   </Row>
  </>
)
})}

 //correct ans
//remove the empty fragments after solve this key.prop warning problem
export default function App() {
      const posts = [
{ id: 1, title: "First "},
{ id: 2, title: "Second" },
{ id: 3, title: "Third" }
 ];
 {fields.map((field,index)=>{
return(
  <> //empty fragment
   <Row key={index}>
    <Col span={6}>hello</Col>
   </Row>
  </>
)
})}

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

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

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

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

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

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

我为每个键使用 Guid 修复了这个问题:生成 Guid:

guid() {
    return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' +
        this.s4() + '-' + this.s4() + this.s4() + this.s4();
}

s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}

然后将此值分配给标记:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM