>[success] # key 属性问什么要加 ~~~ 1.在我们做一些循环的时候,经常需要加key属性,即使不加程序也能运行 ,那么key到底该加不加,应该用什么作为key的绑定,下面结论只代表 我个人的理解,可能有误 ~~~ >[danger] ##### 官方文档对key -- api 解释 ~~~ * 文档中解释: 1.'key' 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。 如果不使用 'key','Vue' 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用 相同类型元素的算法。使用 key,它会基于 'key' 的变化重新排列元素顺序,并且会移除 'key' 不存在的元素。 ~~~ >[info] ## 翻译理解官方意思 ~~~ 1.下面的文章是参考了一下文章整理 2.下面文章中的'diff'算法笔者没有实际去看过具体原理不懂,是根据下面 几篇文章粗滤知道了大概意思,因此可能会出现部分误差 ~~~ 文章参考: [简书作者--Nanshannan《VUE中演示v-for为什么要加key》](https://www.jianshu.com/p/4bd5e745ce95) [思否作者--funnycoderstar 《为什么使用v-for时必须添加唯一的key?》](https://segmentfault.com/a/1190000013810844) [vue官方文档解释](https://cn.vuejs.org/v2/api/#key) [博客园作者-wind文章《详解vue的diff算法》](https://www.cnblogs.com/wind-lanyan/p/9061684.html) [简书作者--小进进不将就《React之diff算法》](https://www.jianshu.com/p/3ba0822018cf) [极客时间--视频Vue实战开发](https://time.geekbang.org/course/detail/163-86448) [vue官方文档--为-v-for-设置键值-必要](https://cn.vuejs.org/v2/style-guide/?#%E4%B8%BA-v-for-%E8%AE%BE%E7%BD%AE%E9%94%AE%E5%80%BC-%E5%BF%85%E8%A6%81) [vue官方文档--没有在-v-if-v-else-if-v-else-中使用-key-谨慎使用](https://cn.vuejs.org/v2/style-guide/?#%E6%B2%A1%E6%9C%89%E5%9C%A8-v-if-v-else-if-v-else-%E4%B8%AD%E4%BD%BF%E7%94%A8-key-%E8%B0%A8%E6%85%8E%E4%BD%BF%E7%94%A8) >[danger] ##### 传统的diff 算法 vs vue diff 算法 ~~~ 1.首先解释一个名词'diff' 算法:计算出Virtual DOM中真正变化的部分,并只 针对该部分进行原生DOM操作,而非重新渲染整个页面。 2.传统的diff算法,会出现,通过循环递归对节点进行依次对比,算法复杂度 达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节 点,得执行上亿次比较 3.vue的diff 算法如上图所示: 3.1 对树分层比较,两棵树 只对同一层次节点 进行比较。如果该节点不存 在时,则该节点及其子节点会被完全删除,不会再进一步比较。 3.2 只需遍历一次,就能完成整棵DOM树的比较。 4.因此整个O(n^3)复杂度 转化为 O(n)复杂度 ~~~ >[danger] ##### diff 算法已经优化了为啥还要加key * 根据上面的描述简单勾勒出下面几个场景 ~~~ 1.带入一句话,就是同数量没有增加或删除,只是改变顺序是只会触发刷新 ,但有新增和删除会触发新增dom 并且销毁改变的dom 2.key 还可以应用在组件上 3.文章参考中最后一篇官方文,可以注重看四五六的情况来品味 ~~~ * 第一场景移动 ~~~ 1.'F' 'E' 是挂载到'C' 节点上,当我们想把'B','C','D' 节点进行位置上的移动 根据上面讲VUE 会比较同层的节点的改变,这样相当于只要移动'B','C','D', 而不需要,移动'B','C','D' 后再移动'E','F',因为根据vue的规则来说'E','F'没有 变化 ~~~ ![](https://box.kancloud.cn/319723b150ceac6b3be2b69f31b1dc6b_1228x517.png) * 场景二新增删除 ~~~ 1.根据上面的总结3.1条的描述,同层节点中'C'已经被删除,因此,此时新的 节点树中的'C' 被删除,然后到下一层发现'B'节点上有'C' 节点注意此时不会 移动,因为在上一个层级'C'节点已经被删除了,所以此时会创建一个新的'C' 和'E','F'节点 ~~~ ![](https://box.kancloud.cn/a12c927a8a30ccabe97a4e0639559713_1211x567.png) * 场景三删除新增 ~~~ 1.因此会吧'C'节点删除,并且创建'G'节点,然后再接着重新创建'E','F'节点 ~~~ ![](https://box.kancloud.cn/557f36da37f572353b80581cf61c5efb_1171x507.png) * 场景四更新删除新建(三个同类型节点) 且无key ~~~ 1.要注意场景四是三个同类节点,场景1说的是三个不同类,举个列子: 场景一: A 节点相当于'div',B节点相当于'span',C节点相当于'a',D节点相当于 'p标签',场景四:A节点相当于'ul','B1','B2','B3'相当于'li' 2.不仅仅是dom 也可以是组件,相同组件也相当于场景四 3.此时场景四就可以抽象理解成我们在给ul 标签中的 li 使用了 'v-for' 属性 我们拖动了'B2' 移动到了'B1'的位置,发生同级中'B2'和'B1' 进行了移动, 但是'E','F'却需要重新创建 ~~~ ![](https://box.kancloud.cn/c92c57527b5bd9df388359f18a2c7983_1201x456.png) * 场景五更新删除新建(三个同类型节点) 且有key ~~~ 1.要注意场景四是三个同类节点,场景1说的是三个不同类,举个列子: 场景一: A 节点相当于'div',B节点相当于'span',C节点相当于'a',D节点相当于 'p标签',场景四:A节点相当于'ul','B1','B2','B3'相当于'li' 2.不仅仅是dom 也可以是组件,相同组件也相当于场景四 3.此时场景四就可以抽象理解成我们在给ul 标签中的 li 使用了 'v-for' 属性 我们拖动了'B2' 移动到了'B1'的位置,发生同级中'B2'和'B1' 进行了移动, 但是因为有key 了,所以'E','F'就不需要创建直接跟着'B2'一起移动了 ~~~ ![](https://box.kancloud.cn/153f342c860da87d98ec2d1f8cc95d66_1148x472.png) * 场景六有key 插入 ~~~ 1.当有key的时候会直接插入'B4'并且会复用'B2'和'B3' ~~~ ![](https://box.kancloud.cn/1222ea310315de39366361741d5ddb0d_1179x331.png) >[danger] ##### 对场景进行进一步总结 ~~~ 1.根据上面的场景,可以大概勾勒出v-for 针对场景四五六,因为循环出来的 肯定是想相同节点挂载,如果没有用key 就会 出现场景四的效果重新生成 新的挂载节点 2.下面的例子会更加深入说明场景六没有key 的样子 ~~~ * 场景六特别说明 ~~~ 1.当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默 认情况下也是遵循以上原则。 比如一下这个情况: ~~~ ![](https://box.kancloud.cn/b790142bec2535c6e8c3ffc2d1ed76a3_477x191.png) ~~~ 1.我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的: ~~~ ![](https://box.kancloud.cn/7d9f901f597e54a5cc512c84f109c7c9_572x215.png) ~~~ 1.即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率? 所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的 识别此节点,找到正确的位置区插入新的节点。 2.因此有了key,高效的更新虚拟DOM ~~~ >[success] # 注意点key 不要使用index ~~~ 1.v-for 中的最后一个例子有实际说明使用index 作为key的一个bug,不仅如 此,使用index作为key 的时候实际整个代码是没有实现场景六的效果, 而是实现了下面的情况 ~~~ * 使用index 作为key ~~~ 第一个数据可以复用之前的之外,另外三条数据都需要重新渲染; ~~~ ~~~ 之前的数据 之后的数据 key: 0 index: 0 name: test1 key: 0 index: 0 name: test1 key: 1 index: 1 name: test2 key: 1 index: 1 name: 我是插队的那条数据 key: 2 index: 2 name: test3 key: 2 index: 2 name: test2 key: 3 index: 3 name: test3 ~~~ * 使用唯一元素例如id ~~~ 1.现在对比发现只有一条数据变化了,就是id为4的那条数据,因此只要新渲染 这一条数据就可以了,其他都是就复用之前的; ~~~ ~~~ 之前的数据 之后的数据 key: 1 id: 1 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1 key: 2 id: 2 index: 1 name: test2 key: 4 id: 4 index: 1 name: 我是插队的那条数据 key: 3 id: 3 index: 2 name: test3 key: 2 id: 2 index: 2 name: test2 key: 3 id: 3 index: 3 name: test3 ~~~