The Virtual DOM Diffing Algorithm and Performance
The key attribute primarily serves as a performance optimization mechanism for Vue's virtual DOM. During the reconciliation process, Vue matches existing DOM nodes to virtual nodes based on their keys. If an element's key remains identical and its underlying data has not mutated, Vue skips updating that specific DOM node, minimizing re-renders and boosting execution speed.
Consequences of Using the Index as a Key
When rendering a list without specifying a key, Vue defaults to using the element's iteration index. This default behavior introduces significant inefficiencies and potential state bugs when the list is mutated.
Consider an array of three items: Item A (index 0), Item B (index 1), and Item C (index 2). If a new element, Item X, is inserted between Item A and Item B, the data array updates to [Item A, Item X, Item B, Item C].
Because the keys are tied to the array indices, the DOM node that previously represented Item B (key=1) now corresponds to Item X, and the node for Item C (key=2) now represents Item B. Consequently, Vue is forced to patch the content of the existing DOM nodes for Item B and Item C instead of simply moving them. If these list items were stateful components, the local state of the original Item B would incorrectly persist in the newly inserted Item X node.
Advantages of Unique Identifiers
Assigning a stable, unique key to each node changes the reconciliation outcome entirely. When Item X is inserted, Vue detects that the keys for Item B and Item C remain unchanged. It simply repositions the existing DOM nodes for Item B and Item C and only creates a single new DOM node for Item X. This granular update process avoids redundant re-renders and correctly preserves component state.
Implementation Example
To illustrate, consider a reactive list of product objects where a new item is injected into the middle of the array:
<script setup>
import { ref } from 'vue';
const catalog = ref([
{ uid: 101, label: 'Alpha' },
{ uid: 102, label: 'Beta' },
{ uid: 103, label: 'Gamma' }
]);
const injectItem = () => {
catalog.value.splice(1, 0, { uid: 104, label: 'Inserted' });
};
</script><template>
<ul>
<li v-for="entry in catalog" :key="entry.uid">
{{ entry.label }}
</li>
</ul>
<button @click="injectItem">Add Item</button>
</template>In scenarios where a list remains entirely static after its initial render, omitting the key or using the index might not cause visible issues. However, for any dynamic list that undergoes insertions, deletions, or reorderings, binding the key to a unique and stable identifier is crucial to maintain both rendering performance and component state integrity.