Vue but underthehood: Part 4 - The Identity Crisis
Introduction
Welcome to Part 4 of "Vue but underthehood"! So far, we've covered The Proxy Foundation, The Language Engine, and The Safety Valve. Now, let's explore The Identity Crisis - how Vue manages component and element identity through its key system and virtual DOM diffing algorithm.
Why Identity Matters
Vue needs to know which elements are which when updating the DOM. Without proper identity tracking, Vue might:
- Reuse DOM elements incorrectly
- Lose component state unexpectedly
- Apply transitions and animations improperly
- Perform inefficient DOM operations
The Key Attribute
The key attribute is Vue's primary mechanism for tracking element identity:
<template>
<div>
<!-- ❌ Without keys - Vue might reuse elements incorrectly -->
<input v-for="item in items" :value="item.name" />
<!-- ✅ With keys - Each element has unique identity -->
<input
v-for="item in items"
:key="item.id"
:value="item.name"
/>
</div>
</template>
Internal Key Tracking
Vue's virtual DOM uses keys during the diffing process:
function patchKeyedChildren(
c1: VNode[],
c2: VNode[],
container: RendererElement,
parentAnchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null
) {
let i = 0;
const l2 = c2.length;
let e1 = c1.length - 1;
let e2 = l2 - 1;
// 1. Sync from start
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent);
} else {
break;
}
i++;
}
// 2. Sync from end
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container, null, parentComponent);
} else {
break;
}
e1--;
e2--;
}
// 3. Common sequence + mount
// 4. Common sequence + unmount
// 5. Unknown sequence - this is where keys are crucial
}
VNode Type Comparison
Vue determines if two VNodes are "the same" using type and key:
export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
return n1.type === n2.type && n1.key === n2.key;
}
The Longest Increasing Subsequence
For optimal performance, Vue uses the Longest Increasing Subsequence (LIS) algorithm to minimize DOM moves:
function getSequence(arr: number[]): number[] {
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {
j = result[result.length - 1];
if (arr[j] < arrI) {
p[i] = j;
result.push(i);
continue;
}
u = 0;
v = result.length - 1;
// Binary search
while (u < v) {
c = (u + v) >> 1;
if (arr[result[c]] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1];
}
result[u] = i;
}
}
}
u = result.length;
v = result[u - 1];
while (u-- > 0) {
result[u] = v;
v = p[v];
}
return result;
}
Component Instance Identity
Components maintain identity through their instance:
interface ComponentInternalInstance {
uid: number;
type: Component;
parent: ComponentInternalInstance | null;
root: ComponentInternalInstance;
vnode: VNode;
// ... more properties
}
let uid = 0;
function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null
): ComponentInternalInstance {
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type: vnode.type as Component,
parent,
// ...
};
return instance;
}
Key Best Practices
Use Stable, Unique Keys
<script setup lang="ts">
import { ref } from 'vue';
interface User {
id: string; // Stable unique identifier
name: string;
}
const users = ref<User[]>([
{ id: 'user-1', name: 'Alice' },
{ id: 'user-2', name: 'Bob' }
]);
</script>
<template>
<!-- ✅ Use stable IDs -->
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
/>
<!-- ❌ Don't use index as key if list can reorder -->
<UserCard
v-for="(user, index) in users"
:key="index"
:user="user"
/>
</template>
Keys in Transitions
Keys are essential for transitions to work correctly:
<template>
<TransitionGroup name="list" tag="ul">
<li
v-for="item in items"
:key="item.id"
class="list-item"
>
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
Fragment Keys
Vue 3 supports fragments, and you can key them too:
<template>
<template v-for="section in sections" :key="section.id">
<h2></h2>
<p></p>
</template>
</template>
Key Takeaways
- Keys help Vue identify which elements have changed, been added, or removed
- Vue uses keys in its diffing algorithm to optimize DOM updates
- The
isSameVNodeTypefunction checks both type and key - The LIS algorithm minimizes DOM moves when reordering lists
- Component instances have unique UIDs for identity tracking
- Always use stable, unique keys for list items
- Keys are essential for transitions and animations
What's Next?
In Part 5: Reactive Realities, we'll bring everything together by exploring the complete reactivity system, including ref, computed, watch, and effect scheduling.
This is Part 4 of the "Vue but underthehood" series.
