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.

Sơ đồ tối ưu hiệu năng Vapor Mode của Vue 3 minh họa cập nhật DOM trực tiếp không cần Virtual DOM

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 trong nháy mắt

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, block tree — giúp giảm công việc thừa. Chúng mang lại cải thiện đo lường được, 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ế độ:

vue
<!-- 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:

javascript
// 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:

vaporApp.js — Full Vapor application (no VDOM runtime)javascript
import { createVaporApp } from 'vue'
import App from './App.vue'

createVaporApp(App).mount('#app')
hybridApp.js — Mixed VDOM + Vapor componentsjavascript
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 — bảng dữ liệu, dashboard thời gian thực, 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.

Giới hạn của Vapor Mode trong Vue 3.6

Vapor Mode yêu cầu Composition API với <script setup>. Options API, app.config.globalProperties, getCurrentInstance(), 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. Suspense hoạt động khi component Vapor được bọc bên trong một cha VDOM, nhưng không hoạt động trong ứng dụng Vapor thuầ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:

  1. 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ẻ: nó chỉ lật các cờ boolean mà không chạy bất kỳ tính toán nào.
  2. 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.

reactivityComparison.tstypescript
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 bản beta 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.

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 — phản hồi API, đối tượng cấu hình, state đã cache — điều này tạo ra hàng nghìn lớp bọc Proxy thừa. shallowRefshallowReactive giới hạn reactivity ở tham chiếu cấp cao nhất.

shallowReactivity.tstypescript
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.

vue
<!-- 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.

vue
<!-- 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ế), và khả năng tương tác với các thư viện component dựa trên VDOM (Vuetify, PrimeVue) có thể còn gồ ghề trong giai đoạn thử nghiệm.

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 — 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.

Đọc thêm

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 bản beta Vue 3.6 ghi lại mọi API Vapor Mode và giới hạn đã biết.

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.

Kết luận

  • 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> — 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, và defineAsyncComponent để chia tách mã — các mẫu này áp dụng ở cả hai chế độ
  • Vapor Mode yêu cầu Composition API với <script setup>. Options API, getCurrentInstance()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 beta vào đầu 2026 — hãy coi Vapor Mode là thử nghiệm cho ứng dụng production, nhưng bắt đầu xây dựng sự quen thuộc cho đội ngũ ngay từ bây giờ

Thẻ

#vue
#vue-3
#vapor-mode
#performance
#virtual-dom
#alien-signals
#reactivity

Chia sẻ

Bài viết liên quan