>[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
~~~
- Vue--基础篇章
- Vue -- 介绍
- Vue -- MVVM
- Vue -- 创建Vue实例
- Vue -- 模板语法
- Vue -- 指令用法
- v-cloak -- 遮盖
- v-bind -- 标签属性动态绑定
- v-on -- 绑定事件
- v-model -- 双向数据绑定
- v-for -- 只是循环没那么简单
- 小知识点 -- 计划内属性
- key -- 属性为什么要加
- 案例说明
- v-if/v-show -- 显示隐藏
- v-for 和 v-if 同时使用
- v-pre -- 不渲染大大胡语法
- v-once -- 只渲染一次
- Vue -- class和style绑定
- Vue -- filter 过滤器
- Vue--watch/computed/fun
- watch -- 巧妙利用watch思想
- Vue -- 自定义指令
- Vue -- $方法
- Vue--生命周期
- Vue -- 专属ajax
- Vue -- transition过渡动画
- 前面章节的案例
- 案例 -- 跑马灯效果
- 案例 -- 选项卡内容切换
- 案例-- 筛选商品
- 案例 -- 搜索/删除/更改
- 案例 -- 用computed做多选
- 案例 -- checked 多选
- Vue--组件篇章
- component -- 介绍
- component -- 使用全局组件
- component -- 使用局部组件
- component -- 组件深入
- component -- 组件传值父传子
- component -- 组件传值子传父
- component -- 子传父语法糖拆解
- component -- 父组件操作子组件
- component -- is 动态切换组件
- component -- 用v-if/v-show控制子组件
- component -- 组件切换的动画效果
- component -- slot 插槽
- component -- 插槽2.6
- component -- 组件的生命周期
- component -- 基础组件全局注册
- VueRouter--获取路由参数
- VueRouter -- 介绍路由
- VueRouter -- 安装
- VueRouter -- 使用
- VueRouter--router-link简单参数
- VueRouter--router-link样式问题
- VueRouter--router-view动画效果
- VueRouter -- 匹配优先级
- vueRouter -- 动态路由
- VueRouter -- 命名路由
- VueRouter -- 命名视图
- VueRouter--$router 获取函数
- VueRouter--$route获取参数
- VueRouter--路由嵌套
- VueRouter -- 导航守卫
- VueRouter -- 写在最后
- Vue--模块化方式结构
- webpack--自定义配置
- webpack -- 自定义Vue操作
- VueCli -- 3.0可视化配置
- VueCli -- 3.0 项目目录
- Vue -- 组件升级篇
- Vue -- 组件种类与组件组成
- Vue -- 组件prop、event、slot 技巧
- Vue -- 组件通信(一)
- Vue -- 组件通信(二)
- Vue -- 组件通信(三)
- Vue -- 组件通信(四)
- Vue -- 组件通信(五)
- Vue -- 组件通信(六)
- Vue -- bus非父子组件通信
- Vue -- 封装js插件成vue组件
- vue组件分装 -- 进阶篇
- Vue -- 组件封装splitpane(分割面板)
- UI -- 正式封装
- Vue -- iview 可编辑表格案例
- Ui -- iview 可以同时编辑多行
- Vue -- 了解递归组件
- UI -- 正式使用递归菜单
- Vue -- iview Tree组件
- Vue -- 利用通信仿写一个form验证
- Vue -- 使用自己的Form
- Vue -- Checkbox 组件
- Vue -- CheckboxGroup.vue
- Vue -- Alert 组件
- Vue -- 手动挂载组件
- Vue -- Alert开始封装
- Vue -- 动态表单组件
- Vue -- Vuex组件的状态管理
- Vuex -- 参数使用理解
- Vuex -- state扩展
- Vuex -- getters扩展
- Vuex--mutations扩展
- Vuex -- Action 异步
- Vuex -- plugins插件
- Vuex -- v-model写法
- Vuex -- 更多
- VueCli -- 技巧总结篇
- CLI -- 路由基础
- CLI -- 路由升级篇
- CLI --异步axios
- axios -- 封装axios
- CLI -- 登录写法
- CLI -- 权限
- CLI -- 简单权限
- CLI -- 动态路由加载
- CLI -- 数据性能优化
- ES6 -- 类的概念
- ES6类 -- 基础
- ES6 -- 继承
- ES6 -- 工作实战用类数据管理
- JS -- 适配器模式
- ES7 -- 装饰器(Decorator)
- 装饰器 -- 装饰器修饰类
- 装饰器--修饰类方法(知识扩展)
- 装饰器 -- 装饰器修饰类中的方法
- 装饰器 -- 执行顺序
- Reflect -- es6 自带版本
- Reflect -- reflect-metadata 版本
- 实战 -- 验证篇章(基础)
- 验证篇章 -- 搭建和目录
- 验证篇章 -- 创建基本模板
- 验证篇章 -- 使用
- 实战 -- 更新模型(为了迎合ui升级)
- 实战 -- 模型与接口对接
- TypeSprict -- 基础篇章
- TS-- 搭建(一)webpack版本
- TS -- 搭建(二)直接使用
- TS -- 基础类型
- TS -- 枚举类型
- TS -- Symbol
- TS -- interface 接口
- TS -- 函数
- TS -- 泛型
- TS -- 类
- TS -- 类型推论和兼容
- TS -- 高级类型(一)
- TS -- 高级类型(二)
- TS -- 关于模块解析
- TS -- 声明合并
- TS -- 混入
- Vue -- TS项目模拟
- TS -- vue和以前代码对比
- TS -- vue简单案例上手
- Vue -- 简单弄懂VueRouter过程
- VueRouter -- 实现简单Router
- Vue-- 原理2.x源码简单理解
- 了解 -- 简单的响应式工作原理
- 准备工作 -- 了解发布订阅和观察者模式
- 了解 -- 响应式工作原理(一)
- 了解 -- 响应式工作原理(二)
- 手写 -- 简单的vue数据响应(一)
- 手写 -- 简单的vue数据响应(二)
- 模板引擎可以做的
- 了解 -- 虚拟DOM
- 虚拟dom -- 使用Snabbdom
- 阅读 -- Snabbdom
- 分析snabbdom源码 -- h函数
- 分析snabbdom -- init 方法
- init 方法 -- patch方法分析(一)
- init 方法 -- patch方法分析(二)
- init方法 -- patch方法分析(三)
- 手写 -- 简单的虚拟dom渲染
- 函数表达解析 - h 和 create-element
- dom操作 -- patch.js
- Vue -- 完成一个minVue
- minVue -- 打包入口
- Vue -- new实例做了什么
- Vue -- $mount 模板编译阶段
- 模板编译 -- 分析入口
- 模板编译 -- 分析模板转译
- Vue -- mountComponent 挂载阶段
- 挂载阶段 -- vm._render()
- 挂载阶段 -- vnode
- 备份章节
- Vue -- Nuxt.js
- Vue3 -- 学习
- Vue3.x --基本功能快速预览
- Vue3.x -- createApp
- Vue3.x -- 生命周期
- Vue3.x -- 组件
- vue3.x -- 异步组件???
- vue3.x -- Teleport???
- vue3.x -- 动画章节 ??
- vue3.x -- 自定义指令 ???
- 深入响应性原理 ???
- vue3.x -- Option API VS Composition API
- Vue3.x -- 使用set up
- Vue3.x -- 响应性API
- 其他 Api 使用
- 计算属性和监听属性
- 生命周期
- 小的案例(一)
- 小的案例(二)-- 泛型
- Vue2.x => Vue3.x 导读
- v-for 中的 Ref 数组 -- 非兼容
- 异步组件
- attribute 强制行为 -- 非兼容
- $attrs 包括 class & style -- 非兼容
- $children -- 移除
- 自定义指令 -- 非兼容
- 自定义元素交互 -- 非兼容
- Data选项 -- 非兼容
- emits Option -- 新增
- 事件 API -- 非兼容
- 过滤器 -- 移除
- 片段 -- 新增
- 函数式组件 -- 非兼容
- 全局 API -- 非兼容
- 全局 API Treeshaking -- 非兼容
- 内联模板 Attribute -- 非兼容
- key attribute -- 非兼容
- 按键修饰符 -- 非兼容
- 移除 $listeners 和 v-on.native -- 非兼容
- 在 prop 的默认函数中访问 this -- ??
- 组件使用 v-model -- 非兼容
- 渲染函数 API -- ??
- Slot 统一 ??
- 过渡的 class 名更改 ???
- Transition Group 根元素 -- ??
- v-if 与 v-for 的优先级对比 -- 非兼容
- v-bind 合并行为 非兼容
- 监听数组 -- 非兼容