【问题标题】:Different ways to add a key to JSX element in loop in React在 React 的循环中向 JSX 元素添加键的不同方法
【发布时间】:2019-02-11 05:57:36
【问题描述】:

我已经研究 react 一年多了。我主要使用 .map、.forEach、.filter 或使用 Object.keys 和 Object.values 迭代数组(如果它是一个对象)。

但是向 jsx 元素添加唯一键的不同方法是什么。以下是我到现在为止的习惯

使用数据中的唯一 id 作为 key 到 key prop:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map(item => {
    return <span key={item.id}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}

使用索引作为 key 的 key 属性:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map((item, i) => {
    let keyValue = i+1;
    return <span key={keyValue}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}

除了我上面提到的以及最有效和推荐的方法之外,还有其他方法可以为 jsx 元素添加唯一键吗?

【问题讨论】:

  • 添加它们如何并不重要。重要的是实际内容具有一些唯一标识符,以防元素以不同的顺序重新呈现。 index 几乎总是不正确的,因为索引总是按数字顺序排列,而实际上保证不会出现数据。您不能只是“添加”一个唯一的 jsx 密钥并希望获得最好的结果。除非您的 jsx 内容实际上没有唯一标识符,否则请使用 index,因为它对 React 的协调算法无关紧要,因为重复内容是一种已知的可能性。 docs
  • 谢谢。如果我在数据中没有唯一 id 并且除了使用索引之外,还有其他方法可以添加唯一键吗?
  • 为什么?要么数据是唯一的,并且它具有唯一标识它的东西,要么它没有,并且密钥基本上是无用的
  • 我们正在转向其他主题。我的问题的目的是了解除了使用数据和索引中的 id 之外,是否还有其他方法可以向 jsx 元素添加唯一键。您还说 Keys 是无用的,但是当我们在迭代期间不添加 key prop 时,为什么 react 会向我们显示警告?
  • 随机数呢?还是一个uuid生成器?有很多方法可以创建随机(唯一)值,但它们不能帮助 React 的协调算法确定要保留的内容

标签: javascript arrays reactjs loops key


【解决方案1】:

您可以将 Date.now()index 一起使用,您的代码将是 例如。

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map((item, i) => {
    let keyValue = Date.now()+i;
    return <span key={keyValue}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}

【讨论】:

  • 在每次渲染时,您将拥有不同的键,因此每个子组件都将被重新渲染,这很糟糕,因为它没有必要并且会损害性能。这也可能会弄乱子组件的状态!请不要那样做:-)
【解决方案2】:

选择键的最佳方法是使用一个字符串,该字符串在其兄弟项中唯一标识一个列表项。大多数情况下,您会使用数据中的 ID 作为键:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

当您没有稳定的渲染项目 ID 时,您可以使用项目索引作为键作为最后的手段:

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

另请注意:

数组中使用的键在它们的兄弟中应该是唯一的。但是,它们不需要是全球唯一的。

您的问题的真正答案在这里: https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

有许多库可以生成随机唯一 ID,例如 shortiduuid(这是最流行的一种,只需查看下载次数),或者只需创建自己的生成随机字符串的函数。

您可以将它们直接添加到数组中的对象中

const todos = [
  { 
    id: uuid(),
    text: 'foo',
  }
]

然后像这样迭代:

const todoItems = todos.map(({id, text}) =>
  <li key={id}>
    {text}
  </li>
);

【讨论】:

  • 我不确定使用随机密钥是否是“最佳”选择,但我同意这是最后的手段。以@Manuel V. Battan 的示例为例,可能会出现重新渲染按钮并因此赋予全新随机键的情况。但是,比随机更好的选择是从内容中获取独特的内容并将其用作密钥。您还可以获取内容的组合,然后按照 Benoit 的说法对其进行散列 - 这是一个很好的替代解决方案。所以最好是来自源的唯一 ID,中间是来自内容的 [hash],最终是随机生成。
【解决方案3】:

首先,避免使用随机键

有很多方法可以编写密钥,有些会比其他的表现更好。

要了解我们选择的键如何影响性能,有必要了解 React 的协调算法。

https://reactjs.org/docs/reconciliation.html

tl;dr 引入了一种启发式方法来比较虚拟 DOM 树以进行此比较 O(n),其中 n 个 VDOM 树的节点。这种启发式可以分为以下几点:

  • 不同类型的组件将创建一棵新树:这意味着,在将旧树与新树进行比较时,如果协调器遇到一个节点确实改变了它的类型(例如 &lt;Button /&gt; 到 @987654323 @),将导致我们的 Button 连同它的孩子一起被卸载,并且 NotButton 也和它的孩子一起被安装。
  • 我们可以通过避免重新创建实例来提示 React 如何在 VDOM 上保存实例。这些提示由我们通过键提供。:在决定是否应保留节点中的实例后(因为其类型保持不变),协调器将迭代该节点的子节点以比较它们。

假设现在我们有这个:

<div>
  <Button title="One" />
  <Button title="Two" />
</div>

我们想在下一次渲染时向 DOM 添加一个 Button,比如说

<div>
  <Button title="Zero" />
  <Button title="One" />
  <Button title="Two" />
</div>

算法如下:

  • 在两个 VDOM 中比较 &lt;divs&gt;。由于它们具有相同的类型,因此我们不需要重新创建新树。道具是相同的,因此此时无需对 DOM 进行任何更改。
  • 按钮OneZero 进行比较。 Reconciler 检测到这是一个 props 更改,然后使用此标题更新 DOM。
  • 按钮TwoOne 进行比较。 Reconcilier 还会在此处检测到 props 更改并使用 DOM 来编写此更改。
  • 检测到新的Button 添加为最后一个子节点,因此在 VDOM 中创建一个新的 Button 实例并将此更改写入 DOM。

请注意,这些对 DOM 有很多操作,因为它通过索引比较组件。

现在,我们可以通过让协调器知道这些实例应该被重用来解决这个问题。现在,让我们来看看:

<div>
  <Button title="One" key="One" />
  <Button title="Two" key="Two" />
</div>

我们想在下一次渲染时向 DOM 添加一个 Button,比如说

<div>
  <Button title="Zero" key="Zero" />
  <Button title="One" key="One" />
  <Button title="Two" key"Two" />
</div>

算法如下:

  • 在两个 VDOM 中比较 &lt;divs&gt;。由于它们具有相同的类型,因此我们不需要重新创建新树。道具是相同的,因此此时无需对 DOM 进行任何更改。
  • 带孩子的第一个孩子。 “这是Button”,核对员说。 '并且有一把钥匙'('一')。然后,在新的孩子列表中寻找一个其键相同的孩子。 “哦,我遇到了!”但是协调器意识到 它的 props 没有变化。然后,这个就不需要 DOM 操作了。
  • 第二个Button 会出现同样的情况,它将通过keys 而不是index 进行比较。意识到它是同一个实例并且没有更改任何 props,因此 React 决定不对 DOM 应用更改。
  • 对于带有'Zero'键的Button,由于不存在具有相同键的子,意识到应该在VDOM上创建一个实例,并且这个更改应该写在DOM上.

因此,通过可预测的内容使用键有助于协调器对 DOM 执行更少的操作。健康的键是那些可以从被映射的对象中推断出来的键,比如name,或者id,如果我们将urls转换为&lt;imgs /&gt;,甚至是url

key=index 呢?不会有任何影响,因为默认情况下,reconciler 按位置进行比较,即它的索引。

这些键应该是全局唯一的吗?不必要。这些在兄弟节点中应该是唯一的,因此协调器可以在通过节点的子节点迭代时区分它们。

随机键呢?应该不惜一切代价避免这些。如果每次渲染都改变了一个键,这将导致 React 在 VDOM 上销毁和创建实例(因此,在 DOM 上进行额外的写入),因为在新子级中没有找到带有键的组件,而是一个新的同类型。

如果渲染输出是这样的

<div>
  <Button key={randomGenerator()} />
</div>

然后,每次执行 render 时(例如由于 props/state 更改,或者即使它的父级正在重新渲染并且我们的 shouldComponentUpdate 返回 true),一个新的 randomGenerator() 键将是生成。这将是这样的:

'嘿!我找到了带有F67BMkd== 键的Button,但在下一个中没有找到。我会删除它。 '哦!我遇到了带有SHDSA++5 键的Button!让我们创建一个新的'。

只要协调器告诉一个实例应该被删除和卸载,它的内部状态就会丢失;即使我们再次安装它。 在这种情况下,VDOM 上的实例将不会被保留。

Button 是一样的,但协调器在 DOM 上搞砸了。

希望对你有帮助。

【讨论】:

    【解决方案4】:

    md5 sha1 甚至 sha256 在内容上。

    【讨论】:

    • 这是干什么用的?
    猜你喜欢
    • 2019-03-10
    • 2017-12-22
    • 2019-12-31
    • 1970-01-01
    • 2022-01-06
    • 2020-10-05
    • 2017-07-24
    • 1970-01-01
    • 2016-10-16
    相关资源
    最近更新 更多