在 React 循环中向 JSX 元素添加键的不同方法 [英] Different ways to add a key to JSX element in loop in React

查看:42
本文介绍了在 React 循环中向 JSX 元素添加键的不同方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

但是为 jsx 元素添加唯一键的不同方法是什么?下面是我到现在为止已经习惯的

使用数据中的唯一 id 作为关键道具的关键:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];使成为(){const items = data.map(item => {return <span key={item.id}>{item.name}</span>;}返回(<div>{项目}

)}

使用索引作为关键道具的关键:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];使成为(){const items = data.map((item, i) => {让 keyValue = i+1;return <span key={keyValue}>{item.name}</span>;}返回(<div>{项目}

)}

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

解决方案

首先,避免使用随机密钥.

编写密钥的方法有很多种,有些方法会比其他方法更好.

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

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

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

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

假设现在我们有了这个:

<按钮标题="一个"/><按钮标题=两个"/>

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

<按钮标题=零"/><按钮标题="一个"/><按钮标题=两个"/>

算法如下:

  • 比较两个 VDOM 中的

    .由于它们具有相同的类型,因此我们不需要重新创建新树.道具是相同的,因此此时没有任何更改可应用于 DOM.
  • Button One 对比 Zero.Reconciler 检测到这里有一个 props 更改,然后用这个标题更新 DOM.
  • Button Two 对比 One.Reconcilier 还会检测到这里的 props 更改并使用 DOM 来编写此更改.
  • 检测到一个新的 Button 被添加为最后一个孩子,因此在 VDOM 上创建一个新的 Button 实例并在 DOM 中写入此更改.

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

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

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

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

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

算法如下:

  • 比较两个 VDOM 中的

    .由于它们具有相同的类型,因此我们不需要重新创建新树.道具是相同的,因此此时没有任何更改可应用于 DOM.
  • 带走孩子们的第一个孩子.这是一个 Button",协调者说.'并且有一把钥匙'('One').然后,在新的 children 列表中寻找 key 相同的 children.哦,我遇到了!"但是协调器意识到它的道具没有变化.然后,此操作将不需要 DOM 操作.
  • 同样的情况发生在第二个 Button 上,它将通过 keys 而不是 index 进行比较.意识到它是同一个实例并且没有更改任何道具,因此 React 决定不对 DOM 应用更改.
  • 对于带有'Zero'键的Button,由于不存在具有相同键的孩子,意识到应该在VDOM上创建一个实例,并且这个改变应该写在 DOM 上.

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

key=index 怎么样?不会有任何影响,因为默认情况下,协调器按位置进行比较,即它的索引.

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

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

如果渲染输出像

<按钮键={randomGenerator()}/>

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

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

每当 reconciler 告诉一个实例应该被删除和卸载时,它的内部状态就会丢失;即使我们再次安装它.在这种情况下,不会保留 VDOM 上的实例.

Button 是一样的,但是 reconciler 把 DOM 弄得一团糟.

希望有帮助.

I have been working on react for more than an year now. I have mostly played with iterating an array using .map, .forEach, .filter or using Object.keys and Object.values if it is an object.

But what are the different ways to add a unique key to jsx element. Below is what I have been used to till now

Using unique id from data as key to 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>
  )
}

Using index as key to key prop:

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

Is there any other ways to add a unique key to the jsx element apart from what I have mentioned above and which is most efficient and recommended?

解决方案

First of all, avoid using random keys.

There are a lot of ways to write keys, and some will perform better than others.

To understand how the keys we've chosen impacts on performance, it's necessary to understand React's Reconciliation Algorithm.

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

tl;dr Introduces a heuristic for comparing Virtual DOM trees to make this comparison O(n), with n the nodes of this VDOM tree. This heuristic can be split in these points:

  • Components of different type will create a new tree: This means that, while comparing the old tree with the new one, if the reconciler encounters that a node did change its type (e.g. <Button /> to <NotButton />), will cause to our Button to be unmounted with its children as well, and NotButton to be mounted with its children, as well.
  • We can hint React on how instances are preserved on VDOM, by avoiding recreating them. These hints are provided by us with keys.: After deciding if the instance in a node should be preserved (because its type remains the same), the reconciler will iterate on that node's children to compare them.

Supose now that we have this:

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

And we'd like to add a Button to the DOM on the next render, say

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

The algorithm will go as follows:

  • Compares <divs> in both VDOMs. Since these have the same type, we don't need to recreate the new tree. Props are the same so there are no changes to apply to the DOM at this point.
  • Button One compares against Zero. Reconciler detects that here was a props change, then updates the DOM with this title.
  • Button Two compares against One. Reconcilier also detects a props change here and uses the DOM to write this change.
  • Detects that a new Button is added as last child, so creates a new Button instance at VDOM and write this change at DOM.

Notice that these has many operations on the DOM, because it compared components by their index.

Now, we can fix this behavior by letting know to our reconciler that these instances should be reused. Now, let's have this:

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

And we'd like to add a Button to the DOM on the next render, say

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

The algorithm will go as follows:

  • Compares <divs> in both VDOMs. Since these have the same type, we don't need to recreate the new tree. Props are the same so there are no changes to apply to the DOM at this point.
  • Takes the first child of childrens. 'It's a Button', says the reconciler. 'And has a key' ('One'). Then, seeks for a children whose key is the same in the new children list. 'Oh, I encountered it!' but the reconciler realizes that there is no change on its props. Then, no DOM operations will be needed for this one.
  • The same scenario occurs with the second Button, it will compare by keys instead of by index. Realizes that it's the same instance and no props were changed, so React decides to not apply changes on the DOM.
  • For the Button with 'Zero' key, since there no exists a child with the same key, realizes that an instance should be created at VDOM, and this change should be written on DOM.

So, using keys by predictable contents helps the reconciler to perform less operations on the DOM. Healthy keys are those that can be inferred from the object that is being mapped, like a name, or an id or even an url if we are transforming urls to <imgs />.

What about key=index? Will have no effect, since by default, reconciler compares by position, i.e. its index.

These keys should be globally unique? Not necessarily. These should be unique among siblings, so reconciler can distinguish them while iterating by a node's children.

What about random keys? These should be avoided at all costs. If a key changes on every render, this will be keeping React destroying and creating instances on the VDOM (and hence, making extra writes on the DOM) since a component with a key wasn't found among the new children, but a new one with the same type.

If the render output is like

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

Then, each time render is executed (e.g. due a props/state change, or even if it's parent is being re-rendered and our shouldComponentUpdate returns true), a new randomGenerator() key will be generated. This will go like:

'Hey! I've found a Button with a F67BMkd== key, but none was found in the next one. I'll delete it.' 'Oh! I've encountered a Button with a SHDSA++5 key! Let's create a new one'.

Whenever the reconciler tells that an instance should be deleted and unmounted, its internal state will be lost; even if we mount it again. The instance at VDOM will not be preserved in this case.

The Button was the same, but the reconciler did a mess at DOM.

Hope it helps.

这篇关于在 React 循环中向 JSX 元素添加键的不同方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆