>[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'沒有
變化
~~~

* 場景二新增刪除
~~~
1.根據上面的總結3.1條的描述,同層節點中'C'已經被刪除,因此,此時新的
節點樹中的'C' 被刪除,然后到下一層發現'B'節點上有'C' 節點注意此時不會
移動,因為在上一個層級'C'節點已經被刪除了,所以此時會創建一個新的'C'
和'E','F'節點
~~~

* 場景三刪除新增
~~~
1.因此會吧'C'節點刪除,并且創建'G'節點,然后再接著重新創建'E','F'節點
~~~

* 場景四更新刪除新建(三個同類型節點) 且無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'卻需要重新創建
~~~

* 場景五更新刪除新建(三個同類型節點) 且有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'一起移動了
~~~

* 場景六有key 插入
~~~
1.當有key的時候會直接插入'B4'并且會復用'B2'和'B3'
~~~

>[danger] ##### 對場景進行進一步總結
~~~
1.根據上面的場景,可以大概勾勒出v-for 針對場景四五六,因為循環出來的
肯定是想相同節點掛載,如果沒有用key 就會 出現場景四的效果重新生成
新的掛載節點
2.下面的例子會更加深入說明場景六沒有key 的樣子
~~~
* 場景六特別說明
~~~
1.當某一層有很多相同的節點時,也就是列表節點時,Diff算法的更新過程默
認情況下也是遵循以上原則。
比如一下這個情況:
~~~

~~~
1.我們希望可以在B和C之間加一個F,Diff算法默認執行起來是這樣的:
~~~

~~~
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 合并行為 非兼容
- 監聽數組 -- 非兼容