Maison  >  Article  >  interface Web  >  Qu’est-ce que Key in vue ? Quelle est la différence entre définir la clé et ne pas la définir ?

Qu’est-ce que Key in vue ? Quelle est la différence entre définir la clé et ne pas la définir ?

青灯夜游
青灯夜游avant
2022-05-19 21:09:454202parcourir

Qu'est-ce que Key in vue ? L'article suivant vous présentera le principe de la clé dans vue et parlera de la différence entre définir la clé et ne pas définir la clé. J'espère que cela sera utile à tout le monde !

Qu’est-ce que Key in vue ? Quelle est la différence entre définir la clé et ne pas la définir ?

1. Qu'est-ce que la clé

Avant de commencer, restaurons deux scénarios de travail réels

  • Lorsque nous utilisons v-for, nous devons l'ajouter à l'unité keyv-for时,需要给单元加上key

<ul>
    <li v-for="item in items" :key="item.id">...</li>
</ul>
  • +new Date()生成的时间戳作为key,手动强制触发重新渲染

<Comp :key="+new Date()" />

那么这背后的逻辑是什么,key的作用又是什么?

一句话来讲

key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点。(学习视频分享:vue视频教程

场景背后的逻辑

当我们在使用v-for时,需要给单元加上key

  • 如果不用key,Vue会采用就地复地原则:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。
  • 如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed

+new Date()生成的时间戳作为key,手动强制触发重新渲染

  • 当拥有新值的rerender作为key时,拥有了新key的Comp出现了,那么旧key Comp会被移除,新key Comp触发渲染

二、设置key与不设置key区别

举个例子:
创建一个实例,2秒后往items数组插入数据

<body>
  <div id="demo">
    <p v-for="item in items" :key="item">{{item}}</p>
  </div>
  <script src="../../dist/vue.js"></script>
  <script>
    // 创建实例
    const app = new Vue({
      el: &#39;#demo&#39;,
      data: { items: [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;] },
      mounted () {
        setTimeout(() => { 
          this.items.splice(2, 0, &#39;f&#39;)  // 
       }, 2000);
     },
   });
  </script>
</body>

在不使用key的情况,vue会进行这样的操作:

Qu’est-ce que Key in vue ? Quelle est la différence entre définir la clé et ne pas la définir ?

分析下整体流程:

  • 比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较C,F,相同类型的节点,进行patch,数据不同,发生dom操作
  • 比较D,C,相同类型的节点,进行patch,数据不同,发生dom操作
  • 比较E,D,相同类型的节点,进行patch,数据不同,发生dom操作
  • 循环结束,将E插入到DOM

一共发生了3次更新,1次插入操作

在使用key的情况:vue会进行这样的操作:

  • 比较A,A,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较B,B,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较C,F,不相同类型的节点
    • 比较E、E,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较D、D,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 比较C、C,相同类型的节点,进行patch,但数据相同,不发生dom操作
  • 循环结束,将F插入到C之前

一共发生了0次更新,1次插入操作

通过上面两个小例子,可见设置key能够大大减少对页面的DOM操作,提高了diff效率

设置key值一定能提高diff效率吗?

其实不然,文档中也明确表示 

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素 

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出 

建议尽可能在使用 v-for 时提供 key

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)
            ) || (
                isTrue(a.isAsyncPlaceholder) &&
                a.asyncFactory === b.asyncFactory &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}

🎜🎜Utilisez l'horodatage généré par +new Date() comme key et forcez-le Déclenchez manuellement le nouveau rendu🎜
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    ...
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            ...
        } else if (isUndef(oldEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            ...
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
            ...
        } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
            ...
        } else {
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
            idxInOld = isDef(newStartVnode.key)
                ? oldKeyToIdx[newStartVnode.key]
                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
            if (isUndef(idxInOld)) { // New element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            } else {
                vnodeToMove = oldCh[idxInOld]
                if (sameVnode(vnodeToMove, newStartVnode)) {
                    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                    oldCh[idxInOld] = undefined
                    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
                } else {
                    // same key but different element. treat as new element
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
                }
            }
            newStartVnode = newCh[++newStartIdx]
        }
    }
    ...
}
🎜Alors, quelle est la logique derrière cela et quel est le rôle de la clé ? 🎜🎜En une phrase🎜🎜🎜key est l'ID unique donné à chaque vnode, et c'est aussi une stratégie d'optimisation de diff. Selon la clé, le nœud vnode correspondant peut être trouvé plus précisément et plus rapidement. 🎜 (Partage de vidéos d'apprentissage : vue vidéo tutoriel🎜)🎜

🎜La logique derrière la scène🎜

🎜Lorsque nous utilisons v-for, nous devons ajouter une clé au unit 🎜
    🎜Si les clés ne sont pas utilisées, Vue adoptera le principe sur place : minimiser le mouvement des éléments et essayer de patcher ou de réutiliser les éléments du même type au même endroit approprié dans la mesure du possible. 🎜Si la clé est utilisée, Vue enregistrera l'élément selon l'ordre des clés. Si l'élément qui avait autrefois la clé n'apparaît plus, il sera directement supprimé ou détruit
🎜 Utilisez L'horodatage généré par +new Date() est utilisé comme clé pour déclencher manuellement le nouveau rendu🎜
    🎜Lorsque le nouveau rendu avec la nouvelle valeur est utilisé comme key, Comp avec la nouvelle clé apparaît, puis l'ancienne clé Comp sera supprimée et la nouvelle clé Comp déclenchera le rendu

🎜2. différence entre définir la clé et ne pas définir la clé🎜🎜🎜Exemple Exemple :
Créez une instance et insérez des données dans le tableau items après 2 secondes🎜rrreee🎜Lorsque key code> n'est pas utilisé, <code>vueCette opération sera effectuée : 🎜🎜1. png🎜🎜Analyser le processus global : 🎜
    🎜Comparez A, A, les nœuds du même type, effectuez un patch, mais les données sont les mêmes, dom ne se produit pas. Opération🎜Comparez B, B, nœuds du même type, effectuez patch, mais les données sont les pareil, aucune opération dom ne se produira🎜Comparez C, F, les nœuds du même type, effectuez un patch, les données sont différentes, dom code> l'opération se produit🎜Comparez D, C, les nœuds du même type, effectuez <code>patch, les données sont différentes, l'opération <code>dom se produit🎜 Comparez E, D, nœuds du même type, effectuez patch, les données sont différentes, dom se produit Opération🎜La boucle se termine, insérez E dans DOM
🎜Un total de 3 mises à jour et 1 opération d'insertion ont eu lieu🎜🎜En utilisation Dans le cas de key : vue effectuera cette opération : 🎜
    🎜Comparez A, A, les nœuds du même type et effectuez patch, mais les données sont les mêmes, l'opération dom ne le fait pas se produisent🎜Comparez B, B, les nœuds du même type, effectuez un patch, mais les données sont les mêmes, dom ne se produit pas Opération 🎜Comparez C, F, nœuds de types différents
      🎜Comparez E, E, nœuds du même type, effectuez patch, mais les données sont les mêmes, cela n'arrive pas dom Opération
    🎜Comparez D et D, nœuds du même type, effectuez un patch, mais les données sont les mêmes, dom🎜Comparez C et C, nœuds du même type, effectuez un patch, mais les données sont les mêmes, pas de dom l'opération se produira🎜La boucle se termine et F est inséré avant C
🎜Un total de 0 mise à jour et 1 opération d'insertion ont eu lieu🎜🎜À travers les deux petits exemples ci-dessus, on peut voir que la clé est définie. Cela peut réduire considérablement les opérations DOM sur la page et améliorer l'efficacité diff🎜

🎜La définition de la valeur clé peut-elle définitivement améliorer l'efficacité des différences ? ? 🎜

🎜En fait, ce n'est pas le cas. Le document indique également clairement que 🎜🎜Lorsque Vue.js utilise v-for pour mettre à jour la liste des éléments rendus, la stratégie par défaut est "réutilisation sur place". Si l'ordre des éléments de données est modifié, Vue ne déplacera pas les éléments DOM pour qu'ils correspondent à l'ordre des éléments de données, mais réutilisera simplement chaque élément ici et s'assurera qu'il affiche chaque élément qui a été rendu à un index spécifique 🎜 🎜 Ce mode par défaut est efficace, mais il ne convient que pour le rendu de liste qui ne repose pas sur l'état du sous-composant ou sur l'état temporaire du DOM (par exemple : valeurs d'entrée du formulaire) 🎜🎜Il est recommandé d'utiliser v-for dans la mesure du possible, fournissez une clé lorsque vous parcourez le contenu DOM de sortie est très simple, ou vous vous appuyez délibérément sur le comportement par défaut pour obtenir des améliorations de performances🎜

三、原理分析

源码位置:core/vdom/patch.js 

里判断是否为同一个key,首先判断的是key值是否相等如果没有设置key,那么keyundefined,这时候undefined是恒等于undefined

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)
            ) || (
                isTrue(a.isAsyncPlaceholder) &&
                a.asyncFactory === b.asyncFactory &&
                isUndef(b.asyncFactory.error)
            )
        )
    )
}

updateChildren方法中会对新旧vnode进行diff,然后将比对出的结果用来更新真实的DOM

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    ...
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            ...
        } else if (isUndef(oldEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            ...
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            ...
        } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
            ...
        } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
            ...
        } else {
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
            idxInOld = isDef(newStartVnode.key)
                ? oldKeyToIdx[newStartVnode.key]
                : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
            if (isUndef(idxInOld)) { // New element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            } else {
                vnodeToMove = oldCh[idxInOld]
                if (sameVnode(vnodeToMove, newStartVnode)) {
                    patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                    oldCh[idxInOld] = undefined
                    canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
                } else {
                    // same key but different element. treat as new element
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
                }
            }
            newStartVnode = newCh[++newStartIdx]
        }
    }
    ...
}

(学习视频分享:web前端开发编程基础视频

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer