Fonctionnement de l'attribut key dans le DOM virtuel
L'attribut key joue un rôle central dans l'algorithme de comparaison (diff) du DOM virtuel de Vue.js. Il sert d'identifiant unique pour les VNodes lors de la mise en correspondance entre l'ancien et le nouvel arbre. En l'absence de key, Vue adopte une stratégie de réutilisation sur place, tentant de modifier et de réutiliser les éléments du même type autant que possible. À l'inverse, lorsqu'une key est spécifiée, Vue l'utilise pour réorganiser l'ordre des éléments en fonction de ses changements et supprime définitivement les nœuds dont la clé a disparu. Il est impératif que les clés soient uniques au sein d'un même parent, sous peine de provoquer des erreurs de rendu.
La stratégie de mise à jour sur place et ses limites
Lors du rendu d'une liste via v-for, le comportement par défaut de Vue consiste à mettre à jour les éléments sur place. Si l'ordre des données change, Vue ne déplace pas les nœuds du DOM pour refléter ce changement ; il se contente de mettre à jour le contenu de chaque élément à son index actuel. Bien que cette approche soit très performante, elle pose problème lorsque les éléments de la liste possèdent un état interne ou dépendent de l'état temporaire du DOM, comme les valeurs saisies dans des champs de formulaire.
Pour permettre à Vue de suivre l'identité de chaque nœud et ainsi optimiser le réordonnancement et la réutilisation, il est nécessaire de fournir un attribut key unique. Il est également fortement déconseillé d'utiliser l'index du tableau comme key. Cela ne garantit pas une réutilisation optimale des éléments et peut entraîner des effets de bord similaires à l'absence totale de clé, tout en empêchant les optimisations internes du framework.
Analyse comparative et implémentation
Pour illustrer ces mécanismes, nous allons comparer les performances et les comportements de différentes configurations de listes. L'environnement de test utilise Vue 3 et les mesures de temps sont effectuées via la console du navigateur après un rechargement complet pour éviter les biais d'optimisation.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Benchmark Key Vue</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="zone-application">
<ul>
<li v-for="val in donneesBasiquesSansCle">{{ val }}</li>
</ul>
<ul>
<li v-for="val in donneesBasiquesAvecCle" :key="val">{{ val }}</li>
</ul>
<ul>
<li v-for="bloc in donneesImbriqueesSansCle">
<span v-for="nb in bloc.valeurs" v-if="nb > 5">{{ nb }}</span>
</li>
</ul>
<ul>
<li v-for="bloc in donneesImbriqueesAvecCle" :key="bloc.identifiant">
<span v-for="nb in bloc.valeurs" :key="nb" v-if="nb > 5">{{ nb }}</span>
</li>
</ul>
</div>
<script>
const { createApp } = Vue;
const app = createApp({
data() {
return {
donneesBasiquesSansCle: [10, 20, 30, 40],
donneesBasiquesAvecCle: [10, 20, 30, 40],
donneesImbriqueesSansCle: [
{ identifiant: 'a', valeurs: [1, 2, 8] },
{ identifiant: 'b', valeurs: [4, 9, 6] }
],
donneesImbriqueesAvecCle: [
{ identifiant: 'a', valeurs: [1, 2, 8] },
{ identifiant: 'b', valeurs: [4, 9, 6] }
]
};
}
}).mount('#zone-application');
</script>
</body>
</html>
Cas des listes simples
Pour des listes contenant des données primitives simples, l'absence de key peut parfois s'avérer plus rapide. Sans clé, Vue réutilise directement les nœuds existants et ne fait que modifier leur contenu textuel. Avec une clé, si les valeurs changent radicalement, Vue va supprimer les anciens nœuds et en créer de nouveaux, ce qui implique des opérations DOM plus coûteuses.
// Mise à jour sans clé (Réutilisation sur place)
console.time('MiseAJourSansCle');
app.donneesBasiquesSansCle = [50, 60, 70, 80, 90, 100];
Vue.nextTick(() => console.timeEnd('MiseAJourSansCle'));
// Mise à jour avec clé (Création et suppression de nœuds)
console.time('MiseAJourAvecCle');
app.donneesBasiquesAvecCle = [50, 60, 70, 80, 90, 100];
Vue.nextTick(() => console.timeEnd('MiseAJourAvecCle'));
Gestion des états temporaires du DOM
La réutilisation sur place montre ses limites lorsqu'il s'agit de composants possédant un état local non lié aux données réactives, comme un champ <input>. Si l'ordre des éléments change, les nœuds DOM non liés aux données restent à leur position initiale, créant un décalage visuel et logique. L'ajout d'une key unique force Vue à déplacer correctement l'intégralité du nœud, préservant ainsi l'état temporaire associé.
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Gestion des états DOM</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.texte-important { color: red; font-weight: bold; }
</style>
</head>
<body>
<div id="app-formulaire">
<h3>Sans clé (Comportement par défaut)</h3>
<div v-for="(employe, index) in equipe" :key="index">
<span :class="{ 'texte-important': index === 0 }">{{ employe.nom }}</span>
<input type="text" placeholder="Saisir une note" />
<button @click="decaler(index)" v-if="index < equipe.length - 1">Descendre</button>
</div>
<h3>Avec clé unique</h3>
<div v-for="(employe, index) in equipe" :key="employe.id">
<span :class="{ 'texte-important': employe.id === 1 }">{{ employe.nom }}</span>
<input type="text" placeholder="Saisir une note" />
<button @click="decaler(index)" v-if="index < equipe.length - 1">Descendre</button>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
equipe: [
{ id: 1, nom: 'Alice' },
{ id: 2, nom: 'Bob' },
{ id: 3, nom: 'Charlie' }
]
};
},
methods: {
decaler(ind) {
if (ind >= this.equipe.length - 1) return;
const nouvelleListe = [...this.equipe];
const temp = nouvelleListe[ind];
nouvelleListe[ind] = nouvelleListe[ind + 1];
nouvelleListe[ind + 1] = temp;
this.equipe = nouvelleListe;
}
}
}).mount('#app-formulaire');
</script>
</body>
</html>
Optimisation des structures complexes
L'utilisation de la key devient particulièrement cruciale pour les listes complexes, notamment lors d'opérations de réorganisation, d'insertion ou de suppression au milieu de la liste. Lorsque des directives conditionnelles comme v-if sont utilisées à l'intérieur des boucles, l'absence de clé oblige Vue à réévaluer ces conditions pour chaque nœud réutilisé. En fournissatn une clé, Vue identifie précisément les nœuds à déplacer, évitant ainsi des recalculs inutiles et améliorant significativement les performances de rendu.
// Réorganisation sans clé (Réévaluation des conditions v-if)
console.time('ReorganisationSansCle');
app.donneesImbriqueesSansCle = [
{ identifiant: 'b', valeurs: [4, 9, 6] },
{ identifiant: 'a', valeurs: [1, 2, 8] }
];
Vue.nextTick(() => console.timeEnd('ReorganisationSansCle'));
// Réorganisation avec clé (Déplacement direct des nœuds)
console.time('ReorganisationAvecCle');
app.donneesImbriqueesAvecCle = [
{ identifiant: 'b', valeurs: [4, 9, 6] },
{ identifiant: 'a', valeurs: [1, 2, 8] }
];
Vue.nextTick(() => console.timeEnd('ReorganisationAvecCle'));