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

新手上路,请多包涵

我正在构建一个接受 JSON 数据源并创建可排序表的 React 组件。

每个动态数据行都有一个分配给它的唯一键,但我仍然收到以下错误:

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

检查 TableComponent 的渲染方法。

我的 TableComponent 渲染方法返回:

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

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

row rows 由具有唯一键的组件构建的:

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

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

原文由 Brett DeWoody 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 538
2 个回答

您应该为每个 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

原文由 jmingov 发布,翻译遵循 CC BY-SA 4.0 许可协议

遍历数组时要小心!

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

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

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

了解 key 道具

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

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

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

  • key 并不是真正关于性能,它更多的是关于身份(这反过来会带来更好的性能)。随机分配和变化的值不是身份
  • 在不知道您的数据是如何建模的情况下,我们实际上无法 [自动] 提供密钥。如果您没有 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> 元素没有改变,因为 Item 和键 0 总是在顶部!

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

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

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

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

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


很坏

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

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

原文由 Chris 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题