Vue Vapor Mode — Loại Bỏ Virtual DOM, Tăng Hiệu Năng Gấp 3 Lần
Posted on: 4/23/2026 7:11:03 AM
Table of contents
Mục lục
- Virtual DOM — Giải pháp hay gánh nặng?
- Vapor Mode là gì?
- Cơ chế hoạt động: Từ template đến DOM trực tiếp
- So sánh compilation output: VDOM vs Vapor
- Benchmark & Hiệu năng thực tế
- Tích hợp vào dự án hiện tại
- Giới hạn & những điều cần biết
- Vapor Mode trong bức tranh lớn của frontend
- Kết luận
Virtual DOM — Giải pháp hay gánh nặng?
Khi React giới thiệu Virtual DOM vào năm 2013, đây được coi là bước đột phá trong lập trình UI. Ý tưởng đơn giản nhưng mạnh mẽ: thay vì thao tác trực tiếp trên DOM thật (chậm), ta xây dựng một bản sao nhẹ (Virtual DOM), so sánh (diff) bản cũ và mới, rồi chỉ cập nhật những phần thay đổi (patch).
Vue.js kế thừa mô hình này từ version 2, và Virtual DOM đã phục vụ tốt trong nhiều năm. Tuy nhiên, khi ứng dụng ngày càng phức tạp, chi phí ẩn của VDOM bắt đầu lộ rõ:
- Memory overhead: Mỗi component tạo ra một cây VNode trong bộ nhớ — dù chỉ render text tĩnh
- Diffing cost: Thuật toán reconciliation phải duyệt toàn bộ cây VNode mỗi lần state thay đổi
- Runtime size: Engine diff/patch chiếm ~50KB trong baseline bundle của Vue 3
- Hydration penalty: SSR phải tái tạo toàn bộ cây VNode phía client trước khi interactive
flowchart LR
A[State Change] --> B[Create New VNode Tree]
B --> C[Diff Old vs New Tree]
C --> D[Compute Patches]
D --> E[Apply Patches to Real DOM]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style E fill:#16213e,stroke:#fff,color:#fff
Các framework thế hệ mới như Svelte và SolidJS đã chứng minh rằng Virtual DOM không phải là cách duy nhất — và thường không phải cách hiệu quả nhất — để xây dựng UI reactive. Bằng cách dịch (compile) component thành mã DOM trực tiếp tại build time, chúng loại bỏ hoàn toàn overhead runtime của VDOM.
Và đó chính là con đường Vue đang đi với Vapor Mode.
Vapor Mode là gì?
Vapor Mode là một chiến lược compilation thay thế được giới thiệu trong Vue 3.6, cho phép compile component thành mã JavaScript thao tác DOM trực tiếp — bỏ qua hoàn toàn lớp Virtual DOM.
Điểm mấu chốt
Vapor Mode không phải là một framework mới. Nó là một compilation mode bên trong Vue — bạn vẫn viết <template>, <script setup>, Composition API, ref(), computed() hoàn toàn giống cũ. Chỉ có output của compiler thay đổi.
Tên gọi "Vapor" lấy cảm hứng từ ý tưởng "Virtual DOM evaporates" — Virtual DOM bốc hơi. Thay vì tạo VNode tree rồi diff, Vapor compiler phân tích template tại build time và sinh ra mã imperative cập nhật chính xác DOM node cần thay đổi khi reactive state thay đổi.
flowchart LR
A[State Change] --> B[Update Exact DOM Node]
style A fill:#e94560,stroke:#fff,color:#fff
style B fill:#4CAF50,stroke:#fff,color:#fff
Cơ chế hoạt động: Từ template đến DOM trực tiếp
Để hiểu Vapor Mode hoạt động thế nào, hãy theo dõi pipeline compilation từ đầu đến cuối:
flowchart TD
A["SFC (.vue file)"] --> B[Vue SFC Parser]
B --> C[Template AST]
C --> D{Compilation Mode?}
D -->|VDOM Mode| E[VDOM Render Function
createVNode, patch, diff]
D -->|Vapor Mode| F[Vapor Compiler]
F --> G[Static Analysis
Tách dynamic vs static nodes]
G --> H[Code Generation
Imperative DOM operations]
H --> I[Reactive Bindings
Effect đính vào từng node]
style A fill:#e94560,stroke:#fff,color:#fff
style D fill:#ff9800,stroke:#fff,color:#fff
style E fill:#f8f9fa,stroke:#2c3e50,color:#2c3e50
style F fill:#4CAF50,stroke:#fff,color:#fff
style I fill:#16213e,stroke:#fff,color:#fff
Bước 1: Static Analysis
Vapor compiler phân tích template và phân loại từng node thành static (không bao giờ thay đổi) và dynamic (phụ thuộc vào reactive state). Các node static được tạo một lần duy nhất và không bao giờ bị revisit.
Bước 2: Code Generation
Thay vì sinh ra createVNode(), compiler sinh ra các lệnh DOM thuần:
// VDOM mode output (truyền thống)
function render() {
return createVNode('div', null, [
createVNode('h1', null, title.value),
createVNode('p', null, message.value)
])
}
// Vapor mode output (mới)
function render() {
const div = document.createElement('div')
const h1 = document.createElement('h1')
const p = document.createElement('p')
h1.textContent = title.value
p.textContent = message.value
div.append(h1, p)
// Chỉ cập nhật node thực sự thay đổi
watchEffect(() => { h1.textContent = title.value })
watchEffect(() => { p.textContent = message.value })
return div
}
Bước 3: Fine-grained Reactivity
Mỗi biểu thức dynamic được wrap trong một watchEffect riêng biệt, đính trực tiếp vào DOM node cần cập nhật. Khi title thay đổi, chỉ có h1.textContent được gọi — p hoàn toàn không bị ảnh hưởng.
So sánh với SolidJS
Cách tiếp cận này rất giống SolidJS — fine-grained reactivity với DOM trực tiếp. Sự khác biệt: Vue Vapor vẫn dùng ref()/reactive() quen thuộc thay vì createSignal() của Solid, giữ nguyên DX mà Vue developer đã quen.
So sánh compilation output: VDOM vs Vapor
Để thấy rõ sự khác biệt, hãy xem cùng một component đơn giản được compile theo hai mode:
<!-- Component gốc -->
<template>
<button @click="count++">
Count: {{ count }}
</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
| Tiêu chí | VDOM Mode | Vapor Mode |
|---|---|---|
| Output type | Render function trả về VNode | Imperative DOM creation + effect bindings |
| Khi state thay đổi | Tạo VNode mới → diff → patch | Effect chạy → cập nhật trực tiếp textContent |
| Runtime cần thiết | @vue/runtime-dom (~50KB) |
@vue/reactivity + vapor helpers (~6KB) |
| Memory per component | VNode tree + component instance | DOM nodes + reactive effects |
| Static content | Hoisted nhưng vẫn qua diff pipeline | Tạo một lần, không bao giờ revisit |
Benchmark & Hiệu năng thực tế
Dựa trên các benchmark được công bố tại Vue.js Nation 2025 và các test cộng đồng trong 2026:
| Framework | Mount 100K components | Baseline Bundle | Update Strategy |
|---|---|---|---|
| Vue 3 (VDOM) | ~300ms | ~50KB | VDOM diff + patch |
| Vue 3.6 (Vapor) | ~100ms | ~6KB | Direct DOM + effects |
| SolidJS | ~95ms | ~7KB | Fine-grained signals |
| Svelte 5 | ~110ms | ~2KB (+ per-component) | Compiled reactivity |
| React 19 | ~350ms | ~42KB | Fiber + concurrent mode |
Lưu ý về benchmark
Các con số trên là từ synthetic benchmarks (mount/update micro-test). Hiệu năng thực tế trong ứng dụng production phụ thuộc vào kiến trúc, data flow, và cách sử dụng component. Vapor Mode cho lợi ích lớn nhất ở các ứng dụng có nhiều component nhỏ và update thường xuyên.
Tích hợp vào dự án hiện tại
Một trong những điểm mạnh nhất của Vapor Mode là chiến lược opt-in từng component. Bạn không cần rewrite toàn bộ ứng dụng — chỉ cần đánh dấu component nào muốn chạy Vapor:
Cách 1: Flag trong SFC
<!-- Component này sẽ compile ở Vapor mode -->
<script setup vapor>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
Cách 2: Config trong Vite
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
features: {
vaporMode: true // Toàn bộ project dùng Vapor
}
})
]
})
Mixed Component Tree
Vue 3.6 cho phép trộn lẫn VDOM component và Vapor component trong cùng một cây component. Điều này nghĩa là bạn có thể:
graph TD
A["App.vue (VDOM)"] --> B["Header.vue (VDOM)"]
A --> C["Dashboard.vue (Vapor ⚡)"]
A --> D["Sidebar.vue (VDOM)"]
C --> E["Chart.vue (Vapor ⚡)"]
C --> F["DataTable.vue (Vapor ⚡)"]
C --> G["FilterPanel.vue (VDOM)"]
style C fill:#4CAF50,stroke:#fff,color:#fff
style E fill:#4CAF50,stroke:#fff,color:#fff
style F fill:#4CAF50,stroke:#fff,color:#fff
style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style D fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Chiến lược migration khuyến nghị
- Bắt đầu với các leaf components (không có children phức tạp)
- Ưu tiên components render thường xuyên (list items, real-time data)
- Kiểm tra kỹ trước khi chuyển components dùng
v-html, dynamic components, hoặc$refs - Đo performance trước và sau bằng Vue DevTools Profiler
Giới hạn & những điều cần biết
Vapor Mode hiện tại (Vue 3.6) vẫn đang trong giai đoạn experimental. Dưới đây là những giới hạn cần nắm:
| Giới hạn | Chi tiết | Workaround |
|---|---|---|
| Bắt buộc SFC | Chỉ hoạt động với Single File Components (.vue) | Chuyển JSX/render function sang SFC |
Không hỗ trợ v-html |
Dynamic HTML injection chưa được support | Dùng component VDOM cho phần cần v-html |
| Dynamic components hạn chế | <component :is="..."> chưa đầy đủ |
Dùng v-if thay thế khi có thể |
Root-level $refs |
Template refs trên root element chưa ổn định | Wrap trong thêm một element |
| Nuxt chưa hỗ trợ | Nuxt integration đang trong lộ trình | Chỉ dùng trong Vite-based projects |
| Non-deterministic code | Math.random(), Date.now() trong template |
Đưa vào computed hoặc ref |
Vapor Mode trong bức tranh lớn của frontend
Vapor Mode không chỉ là một tính năng kỹ thuật — nó phản ánh xu hướng lớn của frontend development: chuyển công việc từ runtime sang compile time.
Điều đáng chú ý là mỗi framework đi đến compiler-driven approach theo cách riêng:
- Svelte: Ngôn ngữ riêng, compiler-first từ đầu
- SolidJS: JSX + signals, không VDOM từ đầu
- React: Giữ VDOM nhưng thêm compiler layer (React Compiler)
- Vue Vapor: Opt-in mode, cùng tồn tại với VDOM — linh hoạt nhất
Vue Vapor = Best of Both Worlds?
Vue là framework duy nhất cho phép cùng lúc chạy cả VDOM và no-VDOM components trong một ứng dụng. Bạn không phải chọn "tất cả hoặc không" — migrate dần dần, component by component, đo đạc hiệu quả từng bước.
Kết luận
Vue Vapor Mode đánh dấu bước tiến quan trọng trong hành trình tối ưu hiệu năng của Vue.js. Bằng cách loại bỏ Virtual DOM overhead ở compile time, Vapor Mode mang lại hiệu năng ngang hàng SolidJS và Svelte mà không yêu cầu developer thay đổi cách viết code.
Với chiến lược opt-in linh hoạt, mixed component tree, và giảm bundle size từ ~50KB xuống ~6KB, Vapor Mode đặc biệt phù hợp cho:
- Ứng dụng mobile-first cần tải nhanh trên mạng yếu
- Dashboard với hàng trăm component cập nhật real-time
- Micro-frontends cần footprint nhỏ gọn
- SSR/SSG applications muốn giảm hydration cost
Dù vẫn đang experimental, Vapor Mode cho thấy tầm nhìn rõ ràng: tương lai của frontend là compiler-driven, và Vue đang ở vị trí thuận lợi nhất khi vừa giữ backward compatibility, vừa đón đầu xu hướng hiệu năng.
Nguồn tham khảo
Vertical Slice Architecture trên .NET 10 — Tổ chức code theo tính năng
Data Pipeline: Từ Batch đến Streaming trong hệ thống dữ liệu hiện đại
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.