Vue模板中使用v-for時晃跺,不建議將index作為key

Vue模板中使用v-for指令時不建議將index作為:key屬性莉给。今天我在看項目代碼時,發(fā)現(xiàn)有多年開發(fā)經(jīng)驗的前端老鳥也犯這樣的低級錯誤问裕。

今天我們就從其原理上說明為什么不建議將 index 作為 v-forkey逮壁,除非你能確定該 v-for 遍歷的數(shù)組長度始終不會發(fā)生變化,不過在這個需求多變的時代誰能保證產(chǎn)品不會想一出是一出呢粮宛?

當我們在模板里使用了響應(yīng)式變量時窥淆,當變化值發(fā)生變化時,其對應(yīng)的dom對象也會觸發(fā)更新巍杈,這背后就是vue內(nèi)部的 vdom diff 算法過程

如下模板代碼

<ul>
     <li>a</li>
     <li>b</li>
</ul>

生成的 VDOM 大致如下

{
  tag: 'ul',
  children: [
    { tag: 'li', children: [ { vnode: { text: 'a' }}]  },
    { tag: 'li', children: [ { vnode: { text: 'b' }}]  },
  ]
}

之后把兩個li順序改變下

{
  tag: 'ul',
  children: [
    { tag: 'li', children: [ { vnode: { text: 'b' }}]  },
    { tag: 'li', children: [ { vnode: { text: 'a' }}]  },
  ]
}

接下來就是diff算法發(fā)揮作用了忧饭,首先就是響應(yīng)式 更新之后,會調(diào)用渲染 Watcher 的回調(diào)函數(shù)vm._update(vm._render())去驅(qū)動視圖更新筷畦,vm._render() 其實生成的就是 vnode词裤,而 vm._update 就會帶著新的 vnode 去走觸發(fā) patch 過程刺洒。

  • 其中首先判斷的就是節(jié)點是否相同:
    • 不相同:就會直接刪除舊的vnode,渲染新的vnode吼砂;
    • 相同:就要讓節(jié)點盡可能多的復(fù)用逆航;

但是節(jié)點相同就要判斷很多情況,如:vnode是文字的話就直接替換掉文字渔肩;vnode不是的話就要對children進行比較(ul中l(wèi)i做比較)因俐;如果有新vnode,沒有舊的vnode周偎,就需要增加節(jié)點抹剩;如果沒有新vnode,有舊vnode蓉坎,那么就要刪除節(jié)點澳眷;如果新舊節(jié)點都有,就需要diff算法了袍嬉;

  // 舊首節(jié)點
  let oldStartIdx = 0
  // 新首節(jié)點
  let newStartIdx = 0
  // 舊尾節(jié)點
  let oldEndIdx = oldCh.length - 1
  // 新尾節(jié)點
  let newEndIdx = newCh.length - 1

這是用變量把新節(jié)點首尾境蔼,舊節(jié)點的首尾表示灶平,在while中伺通,不斷的對新舊節(jié)點進行比較,直到指針收縮到?jīng)]有節(jié)點可以比較逢享。

其中有一個函數(shù) sameVnode罐监,

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      )
    )
  )
}

它是用來判斷節(jié)點是否可用的關(guān)鍵函數(shù),可以看到瞒爬,判斷是否是 sameVnode弓柱,傳遞給節(jié)點的 key 是關(guān)鍵。

然后我們接著進入 diff 過程侧但,每一輪都是同樣的對比矢空,其中某一項命中了,就遞歸的進入 patchVnode 針對單個 vnode 進行的過程(如果這個 vnode 又有 children禀横,那么還會來到這個 diff children 的過程 ):

  1. 舊首節(jié)點和新首節(jié)點用sameNode 對比屁药;
  2. 舊尾節(jié)點和新尾節(jié)點用sameNode 對比;
  3. 舊首節(jié)點和新尾節(jié)點用sameNode 對比柏锄;
  4. 舊尾節(jié)點和新首節(jié)點用sameNode 對比酿箭;
  5. 如果以上邏輯都匹配不到,再把所有舊子節(jié)點的 key 做一個映射到舊節(jié)點下標的 key -> index 表趾娃,然后用新 vnode 的 key 去找出在舊節(jié)點中可以復(fù)用的位置缭嫡。

然后不停的把匹配到的指針向內(nèi)部收縮,直到新舊節(jié)點有一端的指針相遇(說明這個端的節(jié)點都被patch過了)抬闷。

在指針相遇以后妇蛀,還有兩種比較特殊的情況:

有新節(jié)點需要加入。 如果更新完以后,oldStartIdx > oldEndIdx讥耗,說明舊節(jié)點都被 patch 完了有勾,但是有可能還有新的節(jié)點沒有被處理到。接著會去判斷是否要新增子節(jié)點古程。

有舊節(jié)點需要刪除蔼卡。 如果新節(jié)點先patch完了,那么此時會走 newStartIdx > newEndIdx 的邏輯挣磨,那么就會去刪除多余的舊子節(jié)點雇逞。

我們可以使用 Vue SFC Playground 來演示這個過程

<script setup>
import { ref } from 'vue'

const list= ref(['111', '222', '333', '444'])

const handleAddd = () => {
  list.value.unshift('test' + Math.random().toString(16).slice(2))
}

const handleRemove = index => {
  list.value.splice(index, 1)
}
</script>

<template>
  <ul>
    <li v-for="(item, index) in list" :key="index">
      item: {{item}}
      <button @click="handleRemove(index)">
        刪除
      </button>
    </li>
  </ul>
  <button @click="handleAddd">
    新增
  </button>
</template>

如上代碼所示,我們使用index作為key時茁裙,此時我們在數(shù)組 list 的頭部添加一個元素塘砸,會導(dǎo)致其他li進行不必要的更新。

因list子項的key發(fā)生了變化導(dǎo)致更新

刪除也是如此晤锥,由于li的key發(fā)生變化掉蔬,會導(dǎo)致不必要的更新


發(fā)生變化的子項key會導(dǎo)致dom更新

此時我們將 key 綁定為 item 時,將只更新需要更新的dom

應(yīng)確保綁定的 keylist 中能唯一矾瘾,不與其他項相同

key綁定為唯一確定該項的值時將減少不必要的dom更新
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末女轿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子壕翩,更是在濱河造成了極大的恐慌蛉迹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件放妈,死亡現(xiàn)場離奇詭異北救,居然都是意外死亡,警方通過查閱死者的電腦和手機芜抒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門珍策,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宅倒,你說我怎么就攤上這事攘宙。” “怎么了唉堪?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵模聋,是天一觀的道長。 經(jīng)常有香客問我唠亚,道長链方,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任灶搜,我火速辦了婚禮祟蚀,結(jié)果婚禮上工窍,老公的妹妹穿的比我還像新娘。我一直安慰自己前酿,他們只是感情好患雏,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著罢维,像睡著了一般淹仑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肺孵,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天匀借,我揣著相機與錄音,去河邊找鬼平窘。 笑死吓肋,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瑰艘。 我是一名探鬼主播是鬼,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼紫新!你這毒婦竟也來了均蜜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤弊琴,失蹤者是張志新(化名)和其女友劉穎兆龙,沒想到半個月后杖爽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敲董,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年慰安,在試婚紗的時候發(fā)現(xiàn)自己被綠了腋寨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡化焕,死狀恐怖萄窜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撒桨,我是刑警寧澤查刻,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站凤类,受9級特大地震影響穗泵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谜疤,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一佃延、第九天 我趴在偏房一處隱蔽的房頂上張望现诀。 院中可真熱鬧,春花似錦履肃、人聲如沸仔沿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽封锉。三九已至,卻和暖如春膘螟,著一層夾襖步出監(jiān)牢的瞬間烘浦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工萍鲸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留闷叉,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓脊阴,卻偏偏與公主長得像握侧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嘿期,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容