之前一直说React效率高的原因是因为在把虚拟DOM渲染为真实DOM的时候并不是每一次都渲染所有的,而是会进行一定的算法处理,只重新渲染新的DOM。
这里说到的算法,其实就是diffing
算法,或者说diff
算法,diff算法有一个重要的特点就是会一层层的判断每一层标签的属性和内容是否一致,如果一致就会复用,不一致才会重新生成。
关于循环中key的问题
如何理解上边的说法呢,理解之前,可以先来看一个例子:
1 | class User extends React.Component{ |
在上边的代码中,实际就是一个ul
,里边有根据数据生成的li
。
与此同时,还有一个按钮,点击的时候会改变数据,向里边增加一个内容。
不同是,第一个ul
里,li
的key
用的是数据唯一标识,而第二个ul
里,则是直接用的数组的下标。
当浏览器访问的时候,结果如下图所示:
点击增加按钮之后,页面会被重新渲染,结果如下图:
显然,单纯的从页面效果看不出任何的不同。
但是如果上述代码稍作修改,我们在li
里加入一个input
输入框后,就能够看出效果,修改后代码如下:
1 | class User extends React.Component{ |
再次浏览器访问,同时在input
输入框中输入li
里展示的内容后,效果如图:
然后再点击增加按钮后,会看到页面重新渲染成了这样:
从上图可以明显的看出来,第一个ul
和第二个ul
里的内容已经不一样了。
第一个ul
里后边输入框内的内容还是和li
你展示的一致,但是第二个ul
里输入框的内容已经不再和li
中展示的一样了。
为什么会这样呢?因为我们代码里两个ul
中不同的就是li
的key
,所以也能是因为这个key
的问题。
但是为什么这个key
不同,会导致数据错位呢?这就和diff算法
有关了。
解析数据错位的问题
首先,在第一次访问页面的时候,实际上生成的DOM内容是这样的:
1 | <ul> |
那么当点击增加按钮,在state
中增加了一条数据后,里边的数组就变成了这样:
1 | [ |
状态里的数据发生了改变,就会触发render重新渲染页面,渲染的时候React就会用diff算法进行比较:
- 第一次循环,由于新加的数据放在了数组的开头,因此就会先是最新的这个,当key是id的时候,React就会去看是否之前有一个
key
是3
的li
,结果发现没有,就生成一个新的li
。然后React发现li
里边还有一个input
,由于之前连这个li
都没有,因此自然也就不会有li
里的input
了,因此也同时生成一个新的input
。 - 当key是数组索引的时候,由于和数据无关,第一次循环就是
0
,所以React就会去看之前是否有一个key
是0
的li
,然后就发现之前有一个。但是进一步对比状态里的数据的时候,就会发现内容不一样了,于是就会换成新的内容。之后,React发现li
里还有一个input
,就继续去看之前是否有一个key
是0
的li
里边有一个input
,然后就发现确实是有,但是里边内容没有经过状态变化,所以就直接复用了。 - 之后的二次循环以及三次循环,大致上和上边是一样的,所以最终就导致了数据的错位。
上边的问题正是因为diff算法比较的时候用到了key作为标识,所以在实际开发的时候需要特别注意,类似这种需要定义key的地方,要尽可能的使用数据的唯一标识,否则就很有可能造成数据问题.
即使没有数据问题,也会导致不必要的重新生成真实DOM,从而影响效率。