之所以添加key属性,究其根本是因 diff算法。而在业务开发过程中特别是使用map, forEach 等遍历函数的时候往往随手就将index做为组件的key.
那么:key
到底有什么用? 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用就地复用策略 。 这句话是什么意思?
diff算法
简单的说就是新旧虚拟dom
的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染
key的作用
一句话: key
的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;
举个简单的例子
三胞胎战成一排,你怎么知道谁是老大?
如果老大皮了一下子,和老三换了一下位置,你又如何区分出来?
给他们挂个牌牌,写上老大、老二、老三。
这样就不会认错了。key就是这个作用
先如果说就是头铁,不加key属性的话行不行,答案是行的,但性能开销会大一些,并且会得到一个waring的提示,如下:
可以看到,一般的警告是黄色的,这里直接是红色,也可以想象出我们应该需要在这里添加key,属性来避免无谓的性能开销。
在react中可以使用key来标记一个组件,就类似我们的身份证一样,每个key唯一对应一个组件,这样在后续做diff比较的时候比如兄弟组件的位置交换,排序等操作,就可以快速定位到该组件,那就可以直接将定位到的两个组件互换即可。
如果没有给循环渲染的组件列表传入key做标的话,那就按照,"广度优先,分层比较"的diff算法特点去作比较,比较->发现不一样->删除子组件->新建子组件这样开销很大的操作。
所以在开发过程中,遇到循环渲染组件的时候都会加一个key的属性,而且你会发现子组件是无法获取key的值,因为这个key值是给react内部做标记使用,并非给开发者使用。
index作为key属性
上面看出一般我们开发过程中需要循环渲染一个组件列表,都应该加上一个key 来给react做标记,以此来减小性能上的开销,那在常见业务开发中使map, forEach中的index作为key是否可以呢?首先抛出答案:有些场景下使index做key就是个大坑。实际上这是因为作为key的值需要具有唯一性,而index作为key的话,不能百分百保证唯一的,比如遇到排序这种操作。来看个简单的例子
点击我查看在线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组件也会跟着一起重新渲染
总结
综上可以总结得到以下几点:
循环渲染React组件需要传入key依次来减少性能开销
传入的key需有唯一性,否则某些情况下就是大坑
传入的key具有不可读性,即子组件并不能读取父组件中的key
一般情况下比较两个树的不同算法复杂度都在O(n)的三次方,但在React中的diff算法是O(n)的一次方是一个线性的复杂度,具有很大的提升,当这个算法就有两个假设的前提:
组件的DOM结构相对稳定
类型相同的兄弟节点可以被唯一标识(节点位置顺序发生变化的时候)
一般在业务开发过程中如果通过接口获得的列表数组中有id就可以直接id来作为key,如果没有类似id的唯一标记字段,也可以使用 uuid 或者 shortid 第三方npm包来解决这个问题。