Virtual DOM in Vue — Complete Guide
In this tutorial, you will learn about Virtual Dom in Vue. We cover key concepts, practical examples, and best practices to help you master this topic.
Virtual DOM in Vue uses a reactive dependency system with optimized patching, template-compiled render functions, and fine-grained reactivity for efficient updates.
What You'll Learn
- How Vue's Virtual DOM differs from React's
- How Vue's reactivity system interacts with the Virtual DOM
- How Vue compiles templates to optimized render functions
- How Vue 3's patch flags improve diffing
Why It Matters
Vue's approach to Virtual DOM combines compile-time optimization with runtime reactivity. Understanding this helps you leverage Vue's performance characteristics and avoid common pitfalls.
flowchart LR A[Vue Component] --> B[Template Compiler] B --> C[Optimized Render Function] C --> D[VNode Tree] D --> E[Patch Function] E --> F[DOM Updates] G[Reactive State] --> H[Trigger Effects] H --> C
Vue's Reactivity and the Virtual DOM
Vue tracks dependencies at the property level, not the component level.
// Vue 3 reactivity system
import { ref, reactive, computed, watchEffect } from 'vue';
const count = ref(0);
const user = reactive({ name: 'Alice', age: 30 });
// The render function is wrapped in a watcher
// It re-runs only when its reactive dependencies change
function render() {
// This creates a VNode tree
return h('div', [
h('p', 'Count: ' + count.value),
h('p', 'Name: ' + user.name),
h('p', 'Age: ' + user.age)
]);
}
// When count.value changes:
// 1. The reactive system notifies subscribers
// 2. The render function re-runs
// 3. A new VNode tree is created
// 4. The patch function diffs old and new trees
// 5. Only changed DOM nodes are updated
// If only count changes, user.name and user.age are not re-evaluated
// Only the count paragraph is updated in the DOM
Template Compilation and Patch Flags
Vue's compiler analyzes templates and adds optimization hints to VNodes.
// Vue template:
// <div>
// <h1>{{ title }}</h1>
// <p class="static">Static content</p>
// <p :class="dynamicClass">{{ dynamicText }}</p>
// </div>
// Compiled render function (simplified):
function render(_ctx, _cache) {
return h('div', [
h('h1', _ctx.title), // DYNAMIC: text content
h('p', { class: 'static' }, 'Static content'), // STATIC: never changes
h('p', {
class: _ctx.dynamicClass, // DYNAMIC: class binding
patchFlag: 16 // PatchFlags.CLASS
}, _ctx.dynamicText) // DYNAMIC: text content
])
}
// Patch flags tell the runtime exactly what to check:
// PatchFlags.TEXT = 1 // Only text content changed
// PatchFlags.CLASS = 16 // Only class changed
// PatchFlags.STYLE = 32 // Only style changed
// PatchFlags.PROPS = 8 // Only props changed
// During patching:
// If patchFlag is 1 (TEXT), only update textContent
// No need to check class, style, or other attributes
// This avoids unnecessary attribute comparisons
Static Hoisting
Vue hoists static VNodes outside the render function, so they are created once and reused.
// Vue template:
// <div>
// <span class="static">Always the same</span>
// <span>{{ dynamic }}</span>
// </div>
// Compiled output (simplified):
// Static node is hoisted — created only once
const _hoisted_1 = h('span', { class: 'static' }, 'Always the same');
function render(_ctx, _cache) {
return h('div', [
_hoisted_1, // Reused across renders, no diff needed
h('span', _ctx.dynamic) // Only this needs checking
]);
}
// Static hoisting benefits:
// 1. Static VNodes are created once
// 2. No diffing needed for hoisted nodes
// 3. Less memory allocation per render
// 4. Faster reconciliation for static parts
Vue 3 Patch Algorithm
Vue's patch algorithm handles different VNode types efficiently.
// Simplified Vue 3 patch function
function patch(oldVNode, newVNode, container) {
if (oldVNode === newVNode) {
return; // Same reference, nothing to do
}
if (oldVNode && oldVNode.type !== newVNode.type) {
// Different types: unmount old, mount new
unmount(oldVNode);
oldVNode = null;
}
const { type, shapeFlag } = newVNode;
switch (type) {
case Text:
// Text node: update textContent
processText(oldVNode, newVNode, container);
break;
case Comment:
processComment(oldVNode, newVNode, container);
break;
case Fragment:
// Fragment: patch children only
processFragment(oldVNode, newVNode, container);
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// Regular DOM element
processElement(oldVNode, newVNode, container);
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// Component
processComponent(oldVNode, newVNode, container);
}
}
}
// Element patching with patch flags
function patchElement(oldVNode, newVNode) {
const el = newVNode.el = oldVNode.el;
const { patchFlag, dynamicChildren } = newVNode;
if (patchFlag) {
// Optimized path: only check what the flag indicates
if (patchFlag & PatchFlags.CLASS) {
// Only update className
hostPatchProp(el, 'class', null, newVNode.props.class);
}
if (patchFlag & PatchFlags.STYLE) {
// Only update style
hostPatchProp(el, 'style', null, newVNode.props.style);
}
if (patchFlag & PatchFlags.TEXT) {
// Only update textContent
el.textContent = newVNode.children;
}
// No need to check other attributes
} else {
// Full diff path (for dynamic templates)
patchProps(oldVNode.props, newVNode.props, el);
}
// Patch children if not optimized
if (!newVNode.dynamicChildren) {
patchChildren(oldVNode, newVNode, el);
}
}
Vue vs React Virtual DOM
Key differences between Vue and React implementations.
// React:
// - Renders entire component tree on any state change
// - Uses Fiber for interruptible rendering
// - Diffing happens at runtime only
// - No compile-time optimizations
// - Concurrent mode for prioritization
// Vue 3:
// - Tracks dependencies at property level
// - Only re-renders components whose dependencies changed
// - Compile-time optimization (patch flags, hoisting)
// - Block tree optimization (dynamic children tracking)
// - No concurrent mode (simpler scheduling)
// Performance characteristics:
// React: More runtime overhead, better for highly dynamic UIs
// Vue: Less runtime overhead due to compile-time optimizations
// Simple benchmark:
function measureReRender() {
const vueComponent = createVueComponent();
const reactComponent = createReactComponent();
// Update a single property
console.time('Vue update');
for (let i = 0; i < 1000; i++) {
vueComponent.count = i;
}
console.timeEnd('Vue update');
console.time('React update');
for (let i = 0; i < 1000; i++) {
reactComponent.setCount(i);
}
console.timeEnd('React update');
}
Common Mistakes
- Assuming Vue re-renders the entire component tree on every state change (it only re-renders components with changed dependencies).
- Mutating reactive objects without using reactive() or ref() wrappers, breaking reactivity detection.
- Expecting v-for without :key to work efficiently (always provide :key for lists).
- Overusing v-if to toggle large templates (v-show is better for frequently toggled elements).
- Not using the Composition API for better tree-shaking and type inference.
Practice Questions
- How does Vue track reactive dependencies? At the property level using Proxy-based reactivity.
- What are patch flags in Vue 3? Compile-time optimization hints that tell the patcher exactly which properties changed.
- What is static hoisting in Vue? Moving static VNodes outside the render function so they are created once and reused.
- How does Vue 3's block tree optimization work? The compiler tracks dynamic children in an array, skipping static children during diffing.
Challenge
Build a Vue component that demonstrates the compiler's optimization. Create a template with static content, dynamic bindings, and conditional rendering. Use the Vue template explorer to inspect the compiled output and identify patch flags and hoisted nodes.
FAQ
Mini Project
Build a Vue 3 performance comparison tool. Create a component with static and dynamic parts. Measure render time with and without compiler optimizations. Display patch flags, hoisted nodes, and the compiled render function. Show the difference in DOM operations between naive and optimized updates.
What's Next
Lesson 12: Virtual DOM vs No Virtual DOM
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro