Hiệu năng Vue 3 năm 2026: Vapor Mode, Alien Signals và hồi kết của Virtual DOM
Phân tích chuyên sâu về hiệu năng Vapor Mode của Vue 3.6: cách nó loại bỏ Virtual DOM, hệ thống reactivity Alien Signals, các benchmark so với Solid.js và những kỹ thuật tối ưu thực tế cho ứng dụng production.

Vue 3.6 Vapor Mode là thay đổi kiến trúc rendering quan trọng nhất kể từ khi Vue áp dụng Virtual DOM ở phiên bản 2. Bằng cách biên dịch Single File Component trực tiếp thành các thao tác DOM mệnh lệnh, Vapor Mode loại bỏ chi phí diffing vốn định hình pipeline rendering của Vue suốt nhiều năm. Kết hợp với việc viết lại reactivity dựa trên Alien Signals, Vue 3.6 đạt mức ngang bằng về benchmark với Solid.js và Svelte 5, mà không yêu cầu lập trình viên học một API mới.
Vapor Mode là một chiến lược biên dịch tùy chọn trong Vue 3.6, bỏ qua hoàn toàn Virtual DOM. Các component được biên dịch ở Vapor Mode nối từng phụ thuộc reactive trực tiếp tới node DOM chính xác mà nó tác động, tạo ra các cập nhật chuẩn xác mà không cần duyệt cây. Kích hoạt bằng một thuộc tính duy nhất: <script setup vapor>.
Virtual DOM của Vue hoạt động thế nào, và vì sao nó trở thành nút thắt cổ chai
Mẫu Virtual DOM (VDOM), được React phổ biến và Vue 2 áp dụng, tạo ra một biểu diễn JavaScript nhẹ của cây DOM thực tế. Mỗi khi state thay đổi, Vue sinh một cây VDOM mới, so sánh nó với cây trước đó và chỉ vá những node thay đổi vào DOM thực.
Cách tiếp cận này hoạt động tốt với hầu hết ứng dụng. Thuật toán diffing chạy trong thời gian O(n), và trình biên dịch của Vue vốn đã tối ưu các cây con tĩnh ra khỏi đường diff. Nhưng chi phí dồn lại trong những tình huống cụ thể:
- Danh sách lớn với hàng trăm hàng kích hoạt việc so sánh toàn bộ cây con ở mỗi lần cập nhật
- Thay đổi state thường xuyên (hoạt ảnh, dữ liệu thời gian thực) tạo ra VDOM churn mà garbage collector phải dọn dẹp
- Cây component sâu nhân lên chi phí duyệt cây và sinh patch
Trình biên dịch template của Vue 3 đã giới thiệu vài tối ưu VDOM: static hoisting, patch flag, và block tree. Chúng giảm công việc thừa, nhưng kiến trúc nền tảng vẫn yêu cầu sinh, so sánh và loại bỏ các đối tượng JavaScript ở mỗi chu kỳ render.
Vue 3.6 Vapor Mode: biên dịch để loại bỏ Virtual DOM
Vapor Mode áp dụng một cách tiếp cận hoàn toàn khác. Thay vì biên dịch template thành các hàm render trả về node VDOM, trình biên dịch Vapor sinh ra mã trực tiếp tạo và cập nhật các phần tử DOM. Mỗi ràng buộc reactive ánh xạ tới một đột biến DOM cụ thể, không cần biểu diễn trung gian.
Dưới đây là cách một component Vue tiêu chuẩn được biên dịch khác nhau ở mỗi chế độ:
<!-- Counter.vue -->
<script setup vapor>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>
<template>
<button @click="increment">
Count: {{ count }}
</button>
</template>Ở chế độ VDOM cổ điển, template này biên dịch thành một hàm render trả về cây node ảo. Mỗi lần nhấp, Vue tạo một cây VDOM mới, so sánh với cây trước, phát hiện nội dung văn bản thay đổi và vá DOM thực.
Ở Vapor Mode, trình biên dịch sinh ra thứ gần với điều này hơn:
// Simplified Vapor compilation output
const button = document.createElement('button')
const text = document.createTextNode('Count: 0')
button.appendChild(text)
// Direct binding: reactive source -> DOM mutation
effect(() => {
text.nodeValue = \`Count: \${count.value}\`
})
button.addEventListener('click', increment)Hiệu ứng reactive nối count trực tiếp tới text.nodeValue. Không tạo VDOM, không diffing, không patching. Thay đổi state kích hoạt đúng một đột biến DOM.
Bật Vapor Mode trong một dự án
Vapor Mode vận hành ở cấp độ component. Có hai chiến lược tích hợp:
import { createVaporApp } from 'vue'
import App from './App.vue'
createVaporApp(App).mount('#app')import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
createApp(App)
.use(vaporInteropPlugin)
.mount('#app')Cách tiếp cận lai cho phép di chuyển dần dần. Các component quan trọng về hiệu năng, bao gồm bảng dữ liệu, dashboard thời gian thực, và các khung nhìn nhiều hoạt ảnh, có thể chuyển sang Vapor trong khi phần còn lại của ứng dụng vẫn dùng runtime VDOM tiêu chuẩn. Cả hai loại component cùng tồn tại trong một cây component.
Vapor Mode yêu cầu Composition API với <script setup>. Options API, app.config.globalProperties, getCurrentInstance(), v-memo, và các sự kiện lifecycle theo từng phần tử (@vue:mounted, v.v.) không được hỗ trợ. Các directive tùy chỉnh dùng một giao diện khác, nhận các getter reactive thay vì đối tượng binding. Ứng dụng Vapor thuần không hỗ trợ <Suspense>, mặc dù Suspense hoạt động khi component Vapor được bọc bên trong một cha VDOM.
Thay đổi về ủy quyền sự kiện trong Vue 3.6 RC
Giữa bản beta và RC, nhóm phát triển Vue đã thay đổi cách Vapor Mode xử lý sự kiện. Ở bản beta, Vapor tự động ủy quyền sự kiện lên cấp document. Bắt đầu từ RC.2, listener được gắn trực tiếp vào phần tử theo mặc định. Để kích hoạt ủy quyền cấp document nhằm cải thiện hiệu năng với danh sách lớn, hãy dùng modifier .delegate một cách tường minh:
<template>
<ul>
<li v-for="item in items" :key="item.id">
<button @click.delegate="selectItem(item)">
{{ item.name }}
</button>
</li>
</ul>
</template>Thay đổi này giúp Vapor phù hợp với ngữ nghĩa Vue chuẩn về stopPropagation() và đáp ứng kỳ vọng của lập trình viên về xử lý sự kiện.
Alien Signals: bộ máy reactivity mới của Vue
Vue 3.6 mang đến một thay đổi lớn thứ hai bên cạnh Vapor Mode: viết lại hoàn toàn @vue/reactivity dựa trên thư viện Alien Signals. Do Johnson Chu tạo ra, Alien Signals triển khai một thuật toán reactivity push-pull giúp giảm cả việc cấp phát bộ nhớ lẫn chi phí tính toán.
Mô hình push-pull hoạt động qua hai pha:
- Pha push: Khi một signal (nguồn reactive) thay đổi, nó lan truyền một cờ "dirty" tới tất cả computed property phụ thuộc. Lần duyệt này rẻ vì nó chỉ lật các cờ boolean mà không chạy bất kỳ tính toán nào.
- Pha pull: Khi một computed property được đọc, nó kiểm tra cờ dirty của mình. Nếu dirty, nó tính lại. Nếu clean, nó trả ngay giá trị đã cache.
Thiết kế này loại bỏ những lần tính lại không cần thiết. Trong hệ reactivity của Vue 3.5, một computed property phụ thuộc ba signal sẽ tính lại mỗi khi bất kỳ signal nào thay đổi, ngay cả khi kết quả vẫn như cũ. Alien Signals chỉ tính lại khi giá trị thực sự được đọc và ít nhất một phụ thuộc đã thay đổi.
import { ref, computed, watch } from 'vue'
const firstName = ref('Jane')
const lastName = ref('Doe')
const isActive = ref(true)
// This computed only recalculates when read AND dirty
const displayName = computed(() => {
return isActive.value
? \`\${firstName.value} \${lastName.value}\`
: 'Inactive User'
})
// Changing isActive marks displayName as dirty
// But no computation runs until something reads displayName.value
isActive.value = false
// NOW the recalculation happens
console.log(displayName.value) // 'Inactive User'Cấu trúc dữ liệu nội bộ cũng thay đổi. Alien Signals thay việc theo dõi phụ thuộc dựa trên Set bằng danh sách liên kết đôi, giảm bộ nhớ cho mỗi phụ thuộc reactive và cải thiện tốc độ duyệt khi dọn dẹp.
Kết quả benchmark: Alien Signals trong thực tế
Các con số từ benchmark RC Vue 3.6 cho thấy cải thiện nhất quán so với Vue 3.5:
| Chỉ số | Vue 3.5 | Vue 3.6 (Alien Signals) | Cải thiện |
|---|---|---|---|
| Bộ nhớ mỗi ref reactive | Cơ sở | -14% | Cấp phát ít hơn |
| Tính lại computed | Cơ sở | -20% trung bình | Ít lần chạy thừa hơn |
| Mount component (100k) | ~300ms | ~100ms | Nhanh gấp 3 lần |
| First Contentful Paint | Cơ sở | điển hình 0,7s | Giảm chi phí framework |
| Kích thước bundle cơ bản | ~16KB | <10KB | -37% |
Những lợi ích này tự động áp dụng cho mọi ứng dụng Vue 3.6. Khác với Vapor Mode vốn cần bật theo từng component, hệ reactivity Alien Signals là mặc định trong Vue 3.6.
Sẵn sàng chinh phục phỏng vấn Vue.js / Nuxt.js?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Các kỹ thuật tối ưu hiệu năng Vue 3 thực tế
Vapor Mode và Alien Signals xử lý chi phí ở cấp framework, nhưng hiệu năng ở cấp ứng dụng vẫn phụ thuộc vào cách các component được cấu trúc. Những kỹ thuật này áp dụng cho cả component VDOM lẫn Vapor. Để tìm hiểu thêm về xây dựng logic có thể tái sử dụng và được tối ưu cho hiệu năng, xem hướng dẫn về composable Vue 3 nâng cao.
Reactivity nông cho cấu trúc dữ liệu lớn
Reactivity sâu bọc mọi thuộc tính lồng nhau trong một Proxy. Với các tập dữ liệu lớn, như phản hồi API, đối tượng cấu hình, và state đã cache, điều này tạo ra hàng nghìn lớp bọc Proxy thừa. shallowRef và shallowReactive giới hạn reactivity ở tham chiếu cấp cao nhất.
import { shallowRef, triggerRef } from 'vue'
interface Product {
id: number
name: string
variants: { sku: string; price: number }[]
}
// Only the ref itself is reactive, not the nested properties
const products = shallowRef<Product[]>([])
// Fetching data — assign the new array to trigger reactivity
async function fetchProducts() {
const response = await fetch('/api/products')
products.value = await response.json() // triggers update
}
// Mutating nested data — must manually trigger
function updatePrice(productId: number, sku: string, newPrice: number) {
const product = products.value.find(p => p.id === productId)
const variant = product?.variants.find(v => v.sku === sku)
if (variant) {
variant.price = newPrice
triggerRef(products) // explicit trigger required
}
}Directive v-memo cho việc render danh sách tốn kém
Với các component VDOM render danh sách dài, v-memo cache kết quả render của cây con dựa trên giá trị phụ thuộc. VDOM bỏ qua hoàn toàn việc diffing đối với các cây con đã memo mà phụ thuộc của chúng không thay đổi. Lưu ý rằng v-memo không khả dụng trong Vapor Mode.
<!-- ProductGrid.vue -->
<script setup>
import { ref } from 'vue'
const products = ref([])
const selectedId = ref(null)
</script>
<template>
<div class="grid">
<div
v-for="product in products"
:key="product.id"
v-memo="[product.id === selectedId, product.price]"
:class="{ selected: product.id === selectedId }"
>
<h3>{{ product.name }}</h3>
<span>{{ product.price }}</span>
</div>
</div>
</template>Mảng v-memo [product.id === selectedId, product.price] báo cho Vue: không render lại mục này trừ khi trạng thái chọn hoặc giá thay đổi. Với danh sách 500 sản phẩm mà chỉ một được chọn, điều này giảm công việc VDOM từ 500 diff cây con xuống còn 2 (mục được chọn trước đó và mục mới được chọn).
Component bất đồng bộ với Suspense để chia tách mã
Lazy-load các component nặng giữ cho bundle ban đầu gọn nhẹ. defineAsyncComponent của Vue kết hợp với Suspense xử lý trạng thái tải một cách khai báo. Để tìm hiểu sâu hơn về các mẫu Suspense và trường hợp biên, xem hướng dẫn về Teleport và Suspense.
<!-- Dashboard.vue -->
<script setup>
import { defineAsyncComponent } from 'vue'
// Heavy charting library loaded only when needed
const AnalyticsChart = defineAsyncComponent(() =>
import('./components/AnalyticsChart.vue')
)
const DataExport = defineAsyncComponent({
loader: () => import('./components/DataExport.vue'),
delay: 200, // show loading after 200ms
timeout: 10000, // fail after 10s
})
</script>
<template>
<Suspense>
<template #default>
<AnalyticsChart />
<DataExport />
</template>
<template #fallback>
<div class="skeleton-loader" />
</template>
</Suspense>
</template>Vapor Mode so với VDOM: khi nào dùng cách tiếp cận nào
Vapor Mode không phải là sự thay thế phổ quát cho Virtual DOM. Mỗi chế độ biên dịch có thế mạnh phù hợp với các hồ sơ component khác nhau.
| Tình huống | Chế độ khuyến nghị | Lý do |
|---|---|---|
| Bảng dữ liệu (1000+ hàng) | Vapor | Loại bỏ chi phí VDOM theo từng hàng |
| Dashboard thời gian thực | Vapor | Cập nhật thường xuyên hưởng lợi từ ràng buộc DOM trực tiếp |
| Component nhiều hoạt ảnh | Vapor | Không gây áp lực GC do VDOM churn |
| Thư viện component VDOM của bên thứ ba | VDOM | Lớp interop làm tăng độ phức tạp |
| Component dùng Options API | VDOM | Vapor yêu cầu Composition API |
| Biểu mẫu có validation phức tạp | Cả hai | Chi phí render tối thiểu ở cả hai chế độ |
| Trang nội dung tĩnh | Cả hai | SSG/SSR đảm nhận phần việc nặng |
Lộ trình di chuyển khuyến nghị: trước tiên hãy profile ứng dụng bằng tab Performance của Vue DevTools. Xác định các component có thời gian render và tần suất re-render cao nhất. Chuyển chúng sang Vapor Mode, đo lường tác động và mở rộng từ đó.
Câu hỏi phỏng vấn: hiệu năng Vue 3 và Vapor Mode
Những câu hỏi này phản ánh điều các đội ngũ kỹ thuật hỏi vào năm 2026 khi đánh giá chuyên môn Vue. Mỗi câu trả lời tóm tắt lập luận kỹ thuật mà người phỏng vấn mong đợi.
H: Vapor Mode giải quyết vấn đề gì, và nó khác gì so với các tối ưu VDOM mà Vue đã có?
Trình biên dịch VDOM của Vue 3 vốn đã tối ưu các cây con tĩnh, thêm patch flag và triển khai block tree để bỏ qua những diff thừa. Điều đó giảm chi phí VDOM nhưng không loại bỏ nó: mỗi thay đổi state vẫn cần tạo node VDOM, duyệt cây và sinh patch. Vapor Mode loại bỏ toàn bộ pipeline này. Trình biên dịch ánh xạ state reactive trực tiếp tới đột biến DOM, nên một thay đổi state kích hoạt đúng các thao tác DOM cần thiết. Không cấu trúc dữ liệu trung gian, không thuật toán diffing, không garbage collection các node VDOM bị loại bỏ.
H: Component Vapor và VDOM có thể cùng tồn tại trong cùng một ứng dụng không?
Có. vaporInteropPlugin cho phép cả hai loại component trong một cây component. Một cha VDOM có thể render con Vapor và ngược lại, với vài lưu ý. Slot Vapor không thể dùng slots.default() bên trong một component VDOM (dùng renderSlot thay thế). Suspense giờ đã được hỗ trợ cho các component Vapor khi chúng được bọc trong cha VDOM, sau các bản vá trong RC.3. Khả năng tương tác với các thư viện component dựa trên VDOM (Vuetify, PrimeVue) nên được kiểm tra kỹ trước khi triển khai production.
H: Giải thích mô hình reactivity push-pull trong Alien Signals của Vue 3.6.
Mô hình push-pull chia các cập nhật reactive thành hai pha. Ở pha push, khi một signal đổi giá trị, hệ thống lan truyền một cờ dirty xuôi xuống qua tất cả computed property phụ thuộc. Việc này rẻ vì chỉ lật các cờ boolean. Ở pha pull, khi một giá trị computed thực sự được đọc, nó kiểm tra xem mình có dirty không. Nếu dirty, nó tính lại từ các phụ thuộc. Nếu clean, nó trả về giá trị đã cache. Điều này tránh việc tính lại sớm các computed property có thể không bao giờ được đọc trong một chu kỳ cập nhật cụ thể.
H: Khi nào nên dùng shallowRef thay vì ref trong một ứng dụng Vue 3?
shallowRef phù hợp khi cấu trúc dữ liệu lớn và chỉ việc gán lại ở cấp cao nhất mới nên kích hoạt reactivity. Các trường hợp sử dụng bao gồm cache phản hồi API, đối tượng cấu hình, và mảng lớn nơi việc đột biến từng phần tử được kiểm soát thủ công bằng triggerRef(). Reactivity sâu bọc mọi thuộc tính lồng nhau trong một Proxy, là chi phí thừa cho dữ liệu sẽ được thay thế toàn bộ thay vì đột biến tại chỗ.
Luyện tập thêm câu hỏi phỏng vấn Vue.js về composable và các mẫu reactivity trên SharpSkill.
Hướng dẫn hiệu năng chính thức của Vue.js đề cập thêm các kỹ thuật tối ưu, bao gồm độ ổn định prop, virtual scrolling và SSR streaming. Ghi chú phát hành RC Vue 3.6 ghi lại API Vapor Mode mới nhất và các giới hạn đã biết.
Nguồn tham khảo
- Ghi chú phát hành RC Vue 3.6: Changelog chính thức ghi lại các thay đổi RC.7 và trạng thái Vapor Mode
- TODOvue: Trạng thái và giới hạn RC Vue 3.6: Phân tích chi tiết các thay đổi breaking trong RC.2/RC.3 bao gồm ủy quyền sự kiện
- Hướng dẫn nâng cấp RC Vue 3.6: Lộ trình di chuyển từ Vue 3.5 lên 3.6 RC
- GitHub Alien Signals: Mã nguồn và tài liệu cho bộ máy reactivity mới
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Ý nghĩa của Vapor Mode Vue 3.6 đối với ứng dụng Production
Vapor Mode biên dịch SFC của Vue thành các thao tác DOM trực tiếp, loại bỏ hoàn toàn việc tạo, diffing và patching Virtual DOM. Bật theo từng component với <script setup vapor>, và không cần di chuyển toàn ứng dụng.
Alien Signals, bộ máy reactivity mặc định mới, giảm 14% mức dùng bộ nhớ và cải thiện hiệu năng computed property nhờ đánh giá lười push-pull.
Dùng shallowRef cho cấu trúc dữ liệu lớn, v-memo cho việc render danh sách tốn kém (chỉ VDOM), và defineAsyncComponent để chia tách mã. Các mẫu này áp dụng ở cả chế độ VDOM lẫn Vapor.
Vapor Mode yêu cầu Composition API với <script setup>. Options API, getCurrentInstance(), v-memo, và globalProperties không được hỗ trợ.
Profile trước bằng Vue DevTools, chuyển các component có tác động cao sang Vapor, đo lường và lặp lại.
Vue 3.6 đang ở giai đoạn release candidate tính đến tháng 9 năm 2026. RC.7 xử lý hydration, KeepAlive, Teleport, và interop Suspense với VDOM. Kiểm tra kỹ trước khi triển khai production, nhưng bề mặt API giờ đã đủ ổn định để đánh giá nghiêm túc.
Bạn có tìm ra lỗi trong Vue.js / Nuxt.js không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 7 tháng 9, 2026
Thẻ
Chia sẻ
Bài viết liên quan

Vue 3 Reactivity Transform trong 2026: $ref, $computed và Câu hỏi Phỏng vấn
Hướng dẫn chuyên sâu về Reactivity Transform trong Vue 3 bao gồm $ref, $computed, và các câu hỏi phỏng vấn thường gặp cho lập trình viên Vue cấp cao.

Vue 3 Composables Nâng Cao: Các Pattern Tái Sử Dụng và Câu Hỏi Phỏng Vấn 2026
Phân tích chuyên sâu các composable nâng cao trong Vue 3 với pattern tái sử dụng, xử lý bất đồng bộ, dependency injection qua provide/inject, form validation và testing. Bao gồm câu hỏi phỏng vấn kỹ thuật cập nhật 2026.

Kiểm thử Vue 3 năm 2026: Vitest, Vue Test Utils và câu hỏi phỏng vấn
Hướng dẫn thực hành về kiểm thử Vue năm 2026: cấu hình Vitest, mount component với Vue Test Utils, kiểm thử composable và Pinia store, mock API, đo độ phủ và những câu hỏi phỏng vấn mà đội tuyển dụng thường đặt ra.