寫React/Vue項目時為什么要在列表組件中寫key,其作用是什么?


寫React/Vue項目時為什么要在列表組件中寫key,其作用是什么?

參考文章:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/1

 

沒有綁定key的情況下,並且在遍歷模板簡單的情況下,會導致虛擬新舊節點對比更快,節點也會復用。而這種復用是就地復用,一種鴨子辯型的復用。

什么是鴨子辯型?

鴨式辯型來自於James Whitecomb Riley的名言:“像鴨子一樣走路並且嘎嘎叫的就叫鴨子。”通過制定規則來判定對象是否實現了這個接口

 

以下為簡單的例子:

<div id="app">
    <div v-for="i in dataList">{{ i }}</div>
</div>

js

var vm = new Vue({
    el: '#app',
    data: {
        dataList: [1, 2, 3, 4, 5]
    }
})

以上的例子,v-for的內容會生成以下的dom節點數組,我們給每一個節點標記一個身份id:

[
    '<div>1</div>', // id: A
    '<div>2</div>', // id: B
    '<div>3</div>', // id: C
    '<div>4</div>', // id: D
    '<div>5</div>', // id: E
]

1. 改變dataList數據,進行數據位置替換,對比改變后的數據。

vm.dataList = [4, 1, 3, 5, 2]; // 數據位置替換

// 沒有key的情況,節點位置不變,但是節點innerText內容更新了
[
    '<div>4</div>', // id: A
    '<div>1</div>', // id: B
    '<div>3</div>', // id: C
    '<div>5</div>', // id: D
    '<div>2</div>', // id: E
]

// 有key的情況,dom節點位置進行了交換,但是內容沒有更新
// <div v-for="i in dataList" :key="i">{{ i }}</div>
[
    '<div>4</div>', // id: D
    '<div>1</div>', // id: A
    '<div>3</div>', // id: C
    '<div>5</div>', // id: E
    '<div>2</div>', // id: B
]

增刪dataList列表項

vm.dataList = [3, 4, 5, 6, 7] // 數據進行增刪

// 1. 沒有key的情況,節點位置不變,內容也更新了
[
    '<div>3</div>', // id: A
    '<div>4</div>', // id: B
    '<div>5</div>', // id: C
    '<div>6</div>', // id: D
    '<div>7</div>', // id: E
]

// 2. 有key的情況,節點刪除了A, B節點,新增了F, G節點
// <div v-for="i in dataList" :key="i"></div>
[
    '<div>3</div>', // id: C
    '<div>4</div>', // id: D
    '<div>5</div>', // id: E
    '<div>6</div>', // id: F
    '<div>7</div>', // id: G
]

從以上來看,不帶有key,並且使用簡單的模板,基於這個前提下,可以更有效的復用節點,diff速度來看也是不帶key更加快速的,因為帶key在增刪節點上有耗時,這就是vue文檔所說的默認模式。但是這個並不是key作用,而是沒有key的情況下可以對節點就地復用,提高性能。

這種模式會帶來一些隱藏的副作用,比如可能不會產生過渡效果,或者在某些節點有綁定數據(表單)狀態,會出現狀態錯位,Vue文檔也說明了這個默認的模式是高效的,但是只適用於不依賴子組件狀態或臨時DOM狀態(例如:表單輸入值)的列表渲染輸出

 

key的作用是什么?

key是給每一個vnode的唯一id,可以依賴key,更准確,更快地拿到oldVnode中對應的vnode節點

1. 更准確

因為帶key就不是就地復用了,在someNode函數 a.key === b.key 對比中可以避免就地復用的情況,所以會更加准確。

2. 更快

利用key的唯一性生成map對象來獲取對應節點,比遍歷方式更快。

 

vue和react都是采用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中(建議先了解一下diff算法過程)

在交叉對比中,當新節點跟舊節點頭尾交叉對比沒有結果時,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點(這里對應的是一個key => index的map映射)。如果沒找到就認為是一個新增節點。而如果沒有key,那么就會采用遍歷查找的方式去找到對應的舊節點。一種是一個map映射,另一種是遍歷查找。相比而言,map映射的速度更快。

vue部分源碼如下:

// vue項目 src/core/vdom/patch.js -488行

// oldCh是一個舊虛擬節點數組
if (isUndef(oldKeyToIdx)) {
    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if (idDef(newStartVnode.key)) {
    // map方式獲取
    idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
    // 遍歷方式獲取
    idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}

// 創建map函數
function createKeyToOldIdx (children, beginIdx, endIdx) {
    let i, key
    const map = {}
    for (i = beginIdx; i <= endIdx; ++i) {
        key = children[i].key
        if (isDef(key)) map[key] = i
    }
    return map
}

// 遍歷尋找
// sameVnode是對比新舊節點是否相同的函數
function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
        const c = oldCh[i]
        if (isDef(c) && sameVnode(node, c)) return i;
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM