了解 React 中数组子级的唯一键.js了解道具key使用道具key

2022-08-29 22:13:50

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

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

我的渲染方法返回:TableComponent

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

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

每个 in 都是从具有唯一键的组件构建的:rowrows

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

是什么导致了唯一键属性错误?


答案 1

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

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

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

检查此示例

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

在示例中,如果我们没有为元素提供键,并且只想更新 ,React 需要重新呈现整行而不是仅呈现元素。<b>object.city

代码如下:

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 文档:密钥


答案 2

迭代数组时要小心!!

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

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

但是,在许多情况下,事实并非如此!这是反模式在某些情况下可能导致不需要的行为


了解道具key

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

话虽如此,人们并不总是需要应用上述内容,只要知道数组是完全静态的。但是,只要有可能,就鼓励应用最佳做法。

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

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

简而言之,a 应该是: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 2Item 1Item 1key={1}key={0}Item 2key={0}

因此,React 认为元素没有改变,因为 with 键总是在顶部!<input>Item0

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

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

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

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

如果我们在上面的代码段中将添加的项推送到数组的末尾,则每个现有项的顺序将始终正确。


很差

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

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

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


非常好

<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