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 isSameVNodeType function 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.