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

2022-10-28 18:01:33

key的作用

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

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

來看個例子

案例

  • 這裡呢,有個ul標籤

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

    • <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)

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

我現在提一個需求

需求

  • 我新增一個按鈕,這個按鈕會給我新增一個 老劉 這個物件(老六)
  • 這個老劉呢,不能新增在 persons陣列的最後面,要在最前面
  • 不然看不出問題

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

    <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>

<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>

測試

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

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

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

增加需求

  • 我現在再來一個需求
  • 給每個 li標籤當中,新增一個input框

再來測試下

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

問題出現

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

測試

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

再測試

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

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

key的工作原理和對比演演算法

這裡使用流程圖講解

分析index作為key

初始化流程

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

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

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

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

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

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

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

資料更新,老劉出現

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

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

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

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

虛擬DOM的對比演演算法

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

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

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

為什麼說效率低

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

分析item.id作為key

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

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

不寫key

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

面試題

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

分為如下幾步回答

虛擬DOM中key 的作用

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

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

開發中如何選擇key