16_Vue列表渲染中key的工作原理和虛擬DOM對比算法

key的作用

  • 粗略的講,key的作用就是給 節點 設置一個 唯一的標識
  • 就像我們人類社會中,每個人的身份證號一樣

image-20221028115921544

  • 在大部分對key要求不是很嚴格的場景下,使用index作為key是沒問題的
  • 但是我們本章要探討的是,其他情況,可能會出現問題的情況

來看個例子

案例

  • 這裡呢,有個ul標籤

  • 在內部,li標籤通過v-for渲染 data當中的persons數組

    • image-20221028120516257

    • <script>
          var vm = new Vue({
            el: '.app',
            data: {
              name: 'wavesbright',
              persons:[
                  {id:1,name:'張三',age:18},
                  {id:2,name:"李四",age:19},
                  {id:3,name:"王五",age:20},
              ],
            },
          });
      </script>
      
  • 得到了三個節點(3個li)

image-20221028120404442

    <div class="app">
        <h1>遍曆數組</h1>
        <ul>
            <li v-for="(item,index) in persons" :key="index">
                {{item.name}} - {{item.age}}
            </li>
        </ul>
    </div>

我現在提一個需求

需求

  • 我添加一個按鈕,這個按鈕會給我添加一個 老劉 這個對象(老六)
  • 這個老劉呢,不能添加在 persons數組的最後面,要在最前面
  • 不然看不出問題

click方法只調用一次哈,多了不好分析,這裡使用的是事件修飾符

image-20221028151050675

    <div class="app">
        <h1>遍曆數組</h1>
        <!-- 添加一個人,叫老劉 -->
        <button @click.once="addPerson">添加</button>
        <ul>
            <li v-for="(item,index) in persons" :key="index">
                {{item.name}} - {{item.age}}
            </li>
        </ul>
    </div>

image-20221028152046132

<script>
    var vm = new Vue({
      el: '.app',
      data: {
        name: 'wavesbright',
        persons:[
            {id:1,name:'張三',age:18},
            {id:2,name:"李四",age:19},
            {id:3,name:"王五",age:20},
        ],
      },
      methods: {
        // 添加成員 == 老劉
        addPerson(){
            // 這是老劉
            var laoliu = {id:4,name:'老劉',age:'xx'}
            // 在數組頂部添加成員
            this.persons.unshift(laoliu)
        }
      },
    });
</script>

測試

感覺沒什麼問題呀,為什麼會講這個呢

10-28-1

警告,錯誤沒有,頁面顯示正常,也沒有報錯

有問題嗎,有,這裏面有個很嚴重的問題

增加需求

  • 我現在再來一個需求
  • 給每個 li標籤當中,添加一個input框
    • image-20221028151659493

再來測試下

10-28-2

沒問題啊,咋地?來,我給你演示下

問題出現

  • 我在添加老劉之前,我先給張三李四王五的input框框中,輸入一些文字
  • 然後添加老劉,我們來看看這次是什麼樣子的

測試

10-28-3

  • 我們希望看到的是什麼? == 老劉出現的時候,是一個空白的input框
  • 但現在問題是什麼?下方的三個兄弟的信息 分別錯位了 為什麼會這樣
  • 那看看 現在我將 :key換成 item.id會怎麼樣

image-20221028152134062

再測試

10-28-4

哦~那我們現在可以總結一個情況

  • 使用index會出現這個問題
  • 但是使用自帶的id就沒這個問題,這是我們現在所遇到的情況

key的工作原理和對比算法

這裡使用流程圖講解

分析index作為key

初始化流程

1、一切的一切都是你寫了這段代碼

image-20221028152501541

2、vue是不是會拿着你的數據生成 虛擬DOM?(並不是一開始就把數據給你變為頁面DOM的,是有流程的)

image-20221028152603638

3、真實DOM上是沒有這個key的,虛擬DOM上必須要有(沒有,vue不能高效工作)

image-20221028152702102

4、上圖這個是已經生成真實DOM了,我們現在還處於虛擬DOM生成的過程中(假設現在頁面還沒有生成這三個li標籤)。現在,在內存當中是不是有這三個虛擬DOM了(3個li標籤)

5、將虛擬DOM轉換為真實DOM

image-20221028152919220

6、請問,用戶是在哪裡操作的數據?虛擬DOM還是真實DOM,用戶輸入的數據,殘留在誰身上了?虛擬還是真實DOM?(用戶操作的全是真實DOM)

image-20221028153050726

這個時候初始化流程就結束了

數據更新,老劉出現

1、新的數據出現了,老劉出現了,老劉是排在所有人的前面

image-20221028153229232

2、隨後,會根據新的數據,生成新虛擬DOM因為數據發生改變了

image-20221028153331033

這個時候,老劉的key就是0了,因為你使用index作為索引,老劉排在最前面(因為我們插在最前面)

重點來了:在目前的整個流程當中,生成了兩份虛擬DOM,vue不會根據上面這個修改後的虛擬DOM進行真實DOM的創建,而是會將兩個虛擬DOM進行一個對比算法,這就是該算法的由來

image-20221028153810783

虛擬DOM的對比算法

對比的時候,依賴着這個key,怎麼對比的呢?請讓我用文字來為你形容

  1. 首先,它來到這個新的虛擬DOM當中,按照順序,先取出第一個(老劉)
    • image-20221028154032326
    • 然後它就問,你的key是多少,老劉回答 0
  2. 接下來,它來到舊的虛擬DOM中,尋找和 老劉擁有相同key值的人 誰呢? 張三吖
    • image-20221028154246110
    • 找打了,咋的呢?
    • 它會對比兩個節點的內容
  3. 怎麼對比的呢?
    1. image-20221028154635165
    2. 拿出 右側的 老劉-30 與 左側的 張三-18 ,一對比,誒,兩邊不相等
    3. image-20221028154731806
    4. 不一樣了,怎麼著?我們可以得出一點,key值為0的兩個虛擬DOM內容不相同
    5. 接下來,因為二者內部都存在 input,開始對比input的內容
    6. image-20221028154850508
  4. 這裡先暫停下,我來問個問題?請問,對比二者的input的時候,它們input的內容是否相等?
    • 答案是相等的,因為這倆個都是虛擬DOM
    • 用戶輸入數據的時候,數據是殘留在真實DOM中的
      • image-20221028153050726
    • 人家是在內存當中對比的虛擬DOM,
    • 最終input這裡對比的結果就是相等的
    • image-20221028155106931
  5. 剛剛對比不一樣(老劉-30與張三-18)的怎麼辦,一樣的(input)怎麼辦呢?
    • 一樣的結果就是 復用
  6. 什麼是復用呢?
    • image-20221028155435461
    • 你看,我老劉這裡有個input,你張三這裡也有個input
    • 我老劉的key是0,你張三的key也是0
    • 那麼作為唯一標識,咱倆是來自同一個體系的
  7. 要不你看這樣吧,你這個張三的虛擬DOM一定轉換過真實DOM(都用對比算法了肯定轉換過)
    • image-20221028155613207
  8. 你看昂,張三的input轉換過真實DOM,那麼我老劉的input與張三的input是一樣的(虛擬DOM中)
    • 那我老劉就沒必要把 li 當中 的 input轉換為 真實DOM了
      • image-20221028155809293
    • 我直接拿 張三的 input(真實) 復用
  9. 也就出現了下面的情況
    • image-20221028155903526
    • 虛擬DOM進行了對比
    • 對比的結果 決定了 還是使用之前 張三的 真實input (不需要將老劉的虛擬input轉換為真實input)
  10. 然鵝,張三的真是input當中,還殘留着用戶的輸入
  11. 那麼,搬用過來的時候,把殘留輸入一起帶過來了
  12. 錯在哪裡
    • 使用index作為key,導致了 復用 的發生,順序亂了
    • 由於這個細節錯誤的出現,導致了錯位的生成
  13. 以此類推,到王五的時候,key = 3在舊的虛擬DOM當中不存在,那麼只有自己生成了
    • image-20221028160351939

我們出現這個錯誤的原因是執行了這個奇葩的需求,但是通過這個需求,我們能對key進行更加深入的理解

為什麼說效率低

image-20221028160837061

  • 我們來看圖,圖裡解釋的很清楚了
  • 二者的input是相等的,在虛擬DOM當中
  • 但是二者的 插值語法這個位置
    • image-20221028160913980
  • 是不相等的,不相等,那麼就沒有辦法採用 復用行為
  • 沒有辦法採用復用行為,那麼 插值語法這裡的真實DOM就需要自己生成
  • 也就是這一塊,是新的虛擬DOM轉換為真實DOM自己生成的
    • image-20221028161023662

分析item.id作為key

image-20221028161150534

通過上面的分析,這裡思路就很清晰了

  1. 還是一樣的,因為這些初始化數據,我們在內存當中生成了虛擬DOM
    • image-20221028161431657
  2. 虛擬DOM轉換為真實DOM
    • image-20221028161439648
  3. 用戶在真實DOM進行操作,殘留了輸入
    • image-20221028161448483
  4. 接下來我們添加了一個成員,老劉
  5. 那麼虛擬DOM被更新了
    • image-20221028161458620
  6. 更新了,那麼就會和舊的虛擬DOM進行對比
    1. 老劉的key是004,舊的DOM當中是沒有key為004的元素的,那麼老劉就需要自己生成
      • image-20221028161517890
    2. 張三的key是001,舊的虛擬DOM當中有嗎?有的,那麼input一樣嗎,一樣的,那就復用唄
    3. 同理李四和王五也是
    4. image-20221028161524804
  7. 所以使用id作為key是不會出現剛剛那個問題的
  8. 效率高嗎,高,因為服用了,錯位了嗎,沒有

不寫key

  • 如果你不寫key,那麼vue在遍歷的時候會默認將索引值index作為key
  • 既然index作為key了,這個DOM身上是有key的,那麼自然不會報錯
  • 既然index作為key了,遇見剛剛那個問題自然會出現問題

面試題

react、vue中的key有什麼作用?

分為如下幾步回答

虛擬DOM中key 的作用

image-20221028161945156

虛擬DOM對比算法(為了復用節點)

image-20221028161959662

用index作為key可能會引發的問題

image-20221028162027090

開發中如何選擇key

image-20221028162044189