React系统学习6(dom的diffing算法)

之前一直说React效率高的原因是因为在把虚拟DOM渲染为真实DOM的时候并不是每一次都渲染所有的,而是会进行一定的算法处理,只重新渲染新的DOM。

这里说到的算法,其实就是diffing算法,或者说diff算法,diff算法有一个重要的特点就是会一层层的判断每一层标签的属性和内容是否一致,如果一致就会复用,不一致才会重新生成。

关于循环中key的问题

如何理解上边的说法呢,理解之前,可以先来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class User extends React.Component{
state={users:[
{id:1,name:'张三',age:20},
{id:2,name:'李四',age:22}
]}

add=()=>{
let ww={id:3,name:'王五',age:25}
this.setState({users:[ww,...this.state.users]})
}

render() {
let {users}=this.state
return (
<div>
<button onClick={this.add}>点击增加王五</button>
<ul>
{
users.map((user)=>{
console.log(user)
return <li key={user.id}>我是{user.name},今年{user.age}</li>
})
}
</ul>
<ul>
{
users.map((user,index)=>{
console.log(user)
return <li key={index}>我是{user.name},今年{user.age}</li>
})
}
</ul>
</div>
)
}
}

ReactDOM.render(<User />,document.getElementById('test'))

在上边的代码中,实际就是一个ul,里边有根据数据生成的li

与此同时,还有一个按钮,点击的时候会改变数据,向里边增加一个内容。

不同是,第一个ul里,likey用的是数据唯一标识,而第二个ul里,则是直接用的数组的下标。

当浏览器访问的时候,结果如下图所示:

点击增加按钮之后,页面会被重新渲染,结果如下图:

显然,单纯的从页面效果看不出任何的不同。

但是如果上述代码稍作修改,我们在li里加入一个input输入框后,就能够看出效果,修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class User extends React.Component{
state={users:[
{id:1,name:'张三',age:20},
{id:2,name:'李四',age:22}
]}

add=()=>{
let ww={id:3,name:'王五',age:25}
this.setState({users:[ww,...this.state.users]})
}

render() {
let {users}=this.state
return (
<div>
<button onClick={this.add}>点击增加王五</button>
<ul>
{
users.map((user)=>{
console.log(user)
return <li key={user.id}>我是{user.name},今年{user.age} <input type="text"/></li>
})
}
</ul>
<ul>
{
users.map((user,index)=>{
console.log(user)
return <li key={index}>我是{user.name},今年{user.age} <input type="text"/></li>
})
}
</ul>
</div>
)
}
}

ReactDOM.render(<User />,document.getElementById('test'))

再次浏览器访问,同时在input输入框中输入li里展示的内容后,效果如图:

然后再点击增加按钮后,会看到页面重新渲染成了这样:

从上图可以明显的看出来,第一个ul和第二个ul里的内容已经不一样了。

第一个ul里后边输入框内的内容还是和li你展示的一致,但是第二个ul里输入框的内容已经不再和li中展示的一样了。

为什么会这样呢?因为我们代码里两个ul中不同的就是likey,所以也能是因为这个key的问题。

但是为什么这个key不同,会导致数据错位呢?这就和diff算法有关了。

解析数据错位的问题

首先,在第一次访问页面的时候,实际上生成的DOM内容是这样的:

1
2
3
4
5
6
7
8
<ul>
<li key='1'>我是张三,今年20 <input type="text"/></li>
<li key='2'>我是李四,今年22 <input type="text"/></li>
</ul>
<ul>
<li key='0'>我是张三,今年20 <input type="text"/></li>
<li key='1'>我是李四,今年22 <input type="text"/></li>
</ul>

那么当点击增加按钮,在state中增加了一条数据后,里边的数组就变成了这样:

1
2
3
4
5
[
{id:3,name:'王五',age:25},
{id:1,name:'张三',age:20},
{id:2,name:'李四',age:22}
]

状态里的数据发生了改变,就会触发render重新渲染页面,渲染的时候React就会用diff算法进行比较:

  1. 第一次循环,由于新加的数据放在了数组的开头,因此就会先是最新的这个,当key是id的时候,React就会去看是否之前有一个key3li,结果发现没有,就生成一个新的li。然后React发现li里边还有一个input,由于之前连这个li都没有,因此自然也就不会有li里的input了,因此也同时生成一个新的input
  2. 当key是数组索引的时候,由于和数据无关,第一次循环就是0,所以React就会去看之前是否有一个key0li,然后就发现之前有一个。但是进一步对比状态里的数据的时候,就会发现内容不一样了,于是就会换成新的内容。之后,React发现li里还有一个input,就继续去看之前是否有一个key0li里边有一个input,然后就发现确实是有,但是里边内容没有经过状态变化,所以就直接复用了。
  3. 之后的二次循环以及三次循环,大致上和上边是一样的,所以最终就导致了数据的错位。

上边的问题正是因为diff算法比较的时候用到了key作为标识,所以在实际开发的时候需要特别注意,类似这种需要定义key的地方,要尽可能的使用数据的唯一标识,否则就很有可能造成数据问题.

即使没有数据问题,也会导致不必要的重新生成真实DOM,从而影响效率。

推荐文章