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

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

问题描述

我已经从事反应超过一年了.我主要使用.map,.forEach,.filter或如果对象是Object.keys和Object.values来迭代数组.

但是向jsx元素添加唯一键的不同方法是什么?这是我到目前为止所习惯的

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

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

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

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元素添加唯一键,并且最有效和推荐的方法是

解决方案

首先,避免使用随机键.

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

要了解我们选择的密钥如何影响性能,有必要了解React的对帐算法.

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

引入了一种启发式方法,用于比较虚拟DOM树与该VDOM树的n个节点进行比较O(n).这种启发式方法可以分为以下几点:

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

现在假设我们有这个:

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

我们想在下一个渲染器上向DOM添加一个按钮,例如

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

算法如下:

  • 在两个VDOM中比较<divs>.由于它们具有相同的类型,因此我们无需重新创建新树.道具是相同的,因此此时没有更改可应用于DOM.
  • 按钮OneZero进行比较.协调器检测到这里是道具更改,然后使用此标题更新DOM.
  • 按钮TwoOne进行比较.协调员还会在此处检测到道具更改,并使用DOM来编写此更改.
  • 检测到新的Button作为最后一个孩子被添加,因此在VDOM上创建一个新的Button实例,并将此更改写入DOM.

请注意,这些操作在DOM上有很多操作,因为它按索引对组件进行了比较.

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

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

我们想在下一个渲染器上向DOM添加一个按钮,例如

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

算法如下:

  • 在两个VDOM中比较<divs>.由于它们具有相同的类型,因此我们无需重新创建新树.道具是相同的,因此此时没有更改可应用于DOM.
  • 带孩子的第一个孩子.调解员说,这是一个Button". 并且有钥匙"(一个").然后,在新的子代列表中寻找密钥相同的子代. 哦,我遇到了!"但协调员意识到道具没有任何变化.然后,无需为此执行DOM操作.
  • 第二个Button发生相同的情况,它将按keys而不是index进行比较.意识到它是同一个实例,并且没有更改道具,因此React决定不在DOM上应用更改.
  • 对于具有零"键的Button,由于不存在具有相同键的子对象,因此意识到应该在VDOM上创建实例,并且此更改应写在DOM上

因此,通过可预测的内容使用键有助于协调器在DOM上执行较少的操作.健康键是可以从正在映射的对象中推断出来的键,例如nameid甚至是url(如果我们将urls转换为<imgs />的话).

key=index呢?将不会产生任何效果,因为默认情况下,协调程序会按位置(即其索引)进行比较.

这些键应该是全局唯一的吗?不必要.这些在兄弟姐妹之间应该是唯一的,以便协调器可以在节点的子节点进行迭代时区分它们.

随机密钥呢?应不惜一切代价避免这些情况.如果每个渲染上都有键更改,这将保持React在VDOM上销毁并创建实例(因此,在DOM上进行额外的写操作),因为在新的子级中没有找到带有键的组件,而是一个新的具有相同的类型.

如果渲染输出类似于

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

然后,每次执行render时(例如,由于道具/状态更改,或者即使重新渲染其父级并且我们的shouldComponentUpdate返回true),也将使用新的randomGenerator()键生成的.会像这样:

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

每当协调程序告诉实例应该删除并卸载时,其内部状态都会丢失;即使我们再次安装它. 在这种情况下,将不会保留VDOM上的实例.

Button是相同的,但是协调器在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天全站免登陆