React中的key与index

描述

在react 开发过程中,遇到循环渲染一个列表组件的时候,往往会给渲染的子组件添加一个key属性,来提高性能,这里涉及两个问题,

  1. 第一个是为什么添加key属性。
  2. 第二个是怎么添加key属性。

之所以添加key属性,究其根本是因为react 中的 diff算法。而在业务开发过程中特别是使用map, forEach 等遍历函数的时候往往随手就将index做为组件的key。这里就分上面的两点来分别说一下。

组件key属性

先如果说就是头铁,不加key属性的话行不行,答案是行的,但性能开销会大一些,并且会得到一个waring的提示,如下:

react的字母索引选择 react index_React中组件为什么加key


可以看到,一般的警告是黄色的,这里直接是红色,也可以想象出我们应该需要在这里添加key,属性来避免无谓的性能开销。

在react中可以使用key来标记一个组件,就类似我们的身份证一样,每个key唯一对应一个组件,这样在后续做diff比较的时候比如兄弟组件的位置交换,排序等操作,就可以快速定位到该组件,那就可以直接将定位到的两个组件互换即可。

如果没有给循环渲染的组件列表传入key做标的话,那就按照,"广度优先,分层比较"的diff算法特点去作比较,比较->发现不一样->删除子组件->新建子组件这样开销很大的操作。

所以在开发过程中,遇到循环渲染组件的时候都会加一个key的属性,而且你会发现子组件是无法获取key的值,因为这个key值是给react内部做标记使用,并非给开发者使用。

index作为key属性

上面看出一般我们开发过程中需要循环渲染一个组件列表,都应该加上一个key 来给react做标记,以此来减小性能上的开销,那在常见业务开发中使map, forEach中的index作为key是否可以呢?首先抛出答案:有些场景下使index做key就是个大坑。实际上这是因为作为key的值需要具有唯一性,而index作为key的话,不能百分百保证唯一的,比如遇到排序这种操作。来看个简单的例子:

react的字母索引选择 react index_React中index与key_02

点击我查看在线Demo

可以通过上面的demo看到,使用map遍历一个数组,RenderItem组件key使用的是index,而在RenderItem中有两个部分组成,一个是展示出父组件传递过来的文案,第二部分是一个输入框。当我们在倒序展示这组数据的时候,会发现展示文案部分由AAA,BBB,CCC变为了 CCC,BBB,AAA但是输入框中的11,22,33并没有变成想象中的33,22,11。当我们将传入RenderItem组件中的key由Index变为list中的id的时候再次点击倒序按钮,就符合我们的预期了。
这是因为在用index作为key的时候,第一次传入的key为0,1,2.第二次传入的key依然还是0,1,2。但实际这时候标记key=0的组件和之前标记key=0的组件不是一个东西。但由于key一样所以react就认为两次组件是一样的,就不会将RenderItem组件全部渲染,只会将父组件传入属性变化的部分(这里就是现实父组件的文案AAA,BBB,CCC)重新渲染。而Input组件则不会重新渲染。
第二次将key换成了列表中的id,这就确定了id的唯一性,第一次传入RenderItem的key是1,2,3第二次则是3,2,1。那就确保了前后两次的id可以对应上。即第二次key为1的组件就是第一次key为1的组件。所以Inputz组件也会跟着一起重新渲染

总结

综上可以总结得到以下几点:

  1. 循环渲染React组件需要传入key依次来减少性能开销
  2. 传入的key需有唯一性,否则某些情况下就是大坑
  3. 传入的key具有不可读性,即子组件并不能读取父组件中的key

一般情况下比较两个树的不同算法复杂度都在O(n)的三次方,但在React中的diff算法是O(n)的一次方是一个线性的复杂度,具有很大的提升,当这个算法就有两个假设的前提:

  1. 组件的DOM结构相对稳定
  2. 类型相同的兄弟节点可以被唯一标识(节点位置顺序发生变化的时候)

一般在业务开发过程中如果通过接口获得的列表数组中有id就可以直接id来作为key,如果没有类似id的唯一标记字段,也可以使用 uuid 或者 shortid 第三方npm包来解决这个问题。