React思考 【key的作用与Diffing】

36 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

摘要

在学习 vue 和 react 时都有用到循环遍历,如果没有设置 key 属性则控制台会有相应的警告甚至报错。因此引发思考,为什么要添加key,以及如何添加key。

Diffing

首先需要引出一个概念:diffing算法。当页面的节点发生更新,他会根据【新数据】生成【新虚拟dom】,然后和【旧虚拟dom】进行比较。

在比较时,diffing会以标签为最小单位进行比较。比较规则如下:

  1. 旧虚拟dom找到了与新虚拟dom相同的key
    • 虚拟dom的内容没有发生变化,则复用旧虚拟dom的内容。
    • 虚拟dom的内容发生变化,则生成新的真实dom,替换旧真实dom。
  2. 旧虚拟dom没找到与新虚拟dom相同的key,根据数据创建新的真实dom,随后渲染到页面上。

虚拟dom与真实dom

  1. 虚拟dom实际上是一个对象
  2. 虚拟dom比较小,真实dom很多属性虚拟dom都不会有
  3. 最后react会把虚拟dom转换为真实dom

key的使用

根据上面的描述可以发现,key需要使用一个唯一标识。

使用索引

在循环中,索引值是一个唯一标识,但是极其不推荐使用,可能会有很多bug产生,如:

数据更新时性能消耗更大

假设一个场景,一个数组内有多个学生对象

[
    {name: 'daodao', age: 21},
    {name: 'chaochao', age: 21}
]

渲染到页面中,虚拟dom如下:

<li key=0>daodao,21</li>
<li key=1>chaochao,21</li>

此时给这个数组前面添加一个数据对象,如下:

[
    {name: 'tietie', age: 21},
    {name: 'daodao', age: 21},
    {name: 'chaochao', age: 21}
]

此时生成新的虚拟dom:

<li key=0>tietie,21</li>
<li key=1>daodao,21</li>
<li key=2>chaochao,21</li>

第一条新虚拟dom和旧虚拟dom进行对比,发现内容不同,于是生成新的真实dom;第二条新虚拟dom和旧虚拟dom进行对比,发现内容不同,于是生成新的真实dom;第三条新虚拟dom和旧虚拟dom进行对比,发现没有该key,于是生成新的真实dom。

通过分析可以得知,明明有两条相同的dom可以复用,因为索引作为key导致需要消耗额外的性能渲染新的真实dom。

错误的dom更新

<ul>
   {
      this.state.persons.map((personObj,index)=>{
           return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
      })
   }
</ul>

查看上方代码,实现的场景是每一项li里放入该项的数据和一个input输入框,在输入框中输入该项的数据,点击添加新的数据按钮后,会出现以下结果:

image.png

会发现数据错乱了,这是为什么呢?

前面也有说过,diffing最小计算标准是标签,先看旧的虚拟dom,如下:

<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>

点击按钮生成新的数据后新的虚拟dom如下:

<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

此时diffing算法运作流程如下:

  1. 查看key为0的虚拟dom,发现内容不一样,生成新的虚拟dom,发现里面还有一个标签,继续比较。比较后发现没有改变,复用这个input表单,包括里面的内容。
  2. 查看key为1的虚拟dom,发现内容不一样,生成新的虚拟dom,发现里面还有一个标签,继续比较。比较后发现没有改变,复用这个input表单,包括里面的内容。
  3. 查看key为3的虚拟dom,发现没有这个key,生成新的虚拟dom。

可以发现,由于使用了索引为key,导致表单信息错乱。

正确用法

key的正确使用方法是设置id、手机号、身份证号、学号这种一一对应的唯一标识。