Vue 3 基礎概念:Vue 2 與 Vue 3 的差異
簡介
隨著前端框架的快速演進,Vue.js 從 2.x 版升級到 3.x 版已成為許多開發團隊的必經之路。Vue 3 不僅在效能上有顯著提升,更引入了全新的 Composition API、更好的 TypeScript 支援以及更靈活的插件機制。對於已經熟悉 Vue 2 的開發者來說,了解兩個版本的差異是平滑遷移、寫出更可維護程式碼的關鍵。
本篇文章將從語法、生命週期、響應式系統、組件註冊、編譯與部署等多個面向,逐項比較 Vue 2 與 Vue 3 的變化,並提供實用的程式碼範例,協助你在實務專案中快速上手 Vue 3。
核心概念
1. 全新的 Composition API
Vue 2 主要依賴 Options API(data、methods、computed、watch 等),隨著組件變得複雜,邏輯會散落在不同的選項中,難以重用。Vue 3 引入 Composition API(setup、ref、reactive、computed、watchEffect 等),讓開發者可以把相關邏輯「組合」在同一個函式內。
// Vue 2 - Options API
export default {
data() {
return { count: 0 }
},
methods: {
inc() { this.count++ }
},
computed: {
double() { return this.count * 2 }
}
}
// Vue 3 - Composition API
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0) // 响应式引用
const inc = () => count.value++ // 方法
const double = computed(() => count.value * 2) // 计算属性
return { count, inc, double } // 暴露給模板使用
}
}
重點:
setup只在組件 創建前 執行一次,返回的屬性會自動注入模板。這使得相關的狀態與行為可以在同一塊程式碼中看到,提升可讀性與可測試性。
2. 改善的響應式系統(Proxy vs Object.defineProperty)
Vue 2 使用 Object.defineProperty 逐屬性劫持,對於深層嵌套的物件或陣列會產生性能瓶頸。Vue 3 改採 ES6 Proxy,一次性代理整個物件,支援 更完整的偵測(包括新增/刪除屬性)且效能提升約 2~3 倍。
// Vue 2 - 只能偵測已存在的屬性
this.$set(this.obj, 'newProp', 123) // 必須手動 set
// Vue 3 - Proxy 自動偵測
const state = reactive({ a: 1 })
state.b = 2 // 新增屬性即被追蹤
實務建議:在 Vue 3 中直接使用
reactive、ref,不需要再使用Vue.set或this.$set。
3. 更好的 TypeScript 支援
Vue 2 的 TypeScript 支援主要靠 vue-class-component 或 vue-property-decorator,寫法較為冗長。Vue 3 從設計階段即考慮 TypeScript,提供 內建型別,defineComponent、PropType 等工具讓型別推斷變得自然。
// Vue 3 + TypeScript
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
msg: {
type: String as PropType<string>,
required: true
},
list: {
type: Array as PropType<number[]>,
default: () => []
}
},
setup(props) {
// props 已被正確推斷為 { msg: string; list: number[] }
return {}
}
})
要點:使用
defineComponent包裹組件,IDE 能即時提供屬性、事件的型別提示。
4. 生命週期 Hook 的變更
Vue 3 為了讓 Composition API 更直觀,將生命週期 Hook 改為 函式形式(onMounted、onBeforeUnmount 等),可在 setup 中直接呼叫。
| Vue 2 | Vue 3 (Composition) |
|---|---|
created |
onCreated (已廢除) |
mounted |
onMounted |
updated |
onUpdated |
destroyed |
onBeforeUnmount / onUnmounted |
// Vue 2
export default {
mounted() {
console.log('mounted')
}
}
// Vue 3 - Composition
import { onMounted } from 'vue'
export default {
setup() {
onMounted(() => {
console.log('mounted')
})
}
}
提醒:
onBeforeMount、onMounted只能在setup中使用;若仍想使用 Options API,仍能保留原本的生命週期寫法。
5. 組件註冊與全局 API 的改寫
Vue 3 把 全局 API(如 Vue.component、Vue.use)移至 app 實例(createApp 返回的物件),讓多個 Vue 應用可以共存於同一頁面。
// Vue 2
import Vue from 'vue'
import MyComponent from './MyComponent.vue'
Vue.component('my-component', MyComponent)
new Vue({ el: '#app' })
// Vue 3
import { createApp } from 'vue'
import App from './App.vue'
import MyComponent from './MyComponent.vue'
const app = createApp(App)
app.component('my-component', MyComponent) // 全局註冊
app.mount('#app')
最佳實踐:盡量使用 局部註冊(在父組件
components中引用)以減少全局污染,提升程式碼可維護性。
6. 編譯與部署:更小的體積與更快的載入
Vue 3 的核心程式碼體積約 50%(約 20KB gzipped),主要得益於:
- Tree‑shaking:模組化設計允許 bundler(如 Vite、Webpack)剔除未使用的 API。
- Fragment、Teleport、Suspense:原生支援多根節點、跨層級渲染與異步組件,減少額外的 wrapper 元素。
# Vue 2
npm install vue@2
# Vue 3
npm install vue@3
實務提示:若使用 Vite 作為開發伺服器,Vue 3 的即時模組熱重載(HMR)速度比 Vue 2 + webpack 更快。
常見陷阱與最佳實踐
| 陷阱 | 可能的情況 | 解決方式 |
|---|---|---|
忘記使用 .value |
在 ref 中讀寫時直接使用變數(例如 count = 1) |
使用 count.value,或在 setup 中透過 toRefs 轉成普通屬性 |
| 混用 Options API 與 Composition API | 同一組件內同時使用 data 與 setup,導致狀態不一致 |
建議 統一 使用一種 API;若必須混用,確保資料來源單一 |
| Proxy 無法偵測陣列索引變化(舊版瀏覽器) | 在 IE11 或舊版 Safari 中使用 Vue 3 | 需使用 polyfill(@vue/reactivity)或降級至 Vue 2 |
| 全局 API 仍使用 Vue 2 方式 | Vue.use(plugin) 直接調用 |
改為 app.use(plugin),並在 createApp 後執行 |
| 組件懶載入寫法錯誤 | 使用 import() 但忘記包裝 defineAsyncComponent |
javascript\nimport { defineAsyncComponent } from 'vue'\nconst AsyncComp = defineAsyncComponent(() => import('./MyComp.vue'))\n |
最佳實踐
- 優先使用 Composition API:對於可重用的業務邏輯,寫成
useXxxHook(如useFetch),在不同組件間直接引用。 - 善用 TypeScript:在
props、emit、expose中明確聲明型別,讓 IDE 能提供即時錯誤檢查。 - 拆分大型組件:利用 Fragment 省去不必要的
<template>包裹,減少 DOM 層級。 - 使用 Teleport 處理全局 UI(如 Modal、Toast),避免在根組件中堆疊大量結構。
- 在測試環境使用 Vue Test Utils v2:它已支援 Vue 3 的新特性,避免因版本不匹配導致測試失敗。
實際應用場景
| 場景 | Vue 2 實作方式 | Vue 3 改寫建議 |
|---|---|---|
| 表單驗證 | 使用 data + watch,每個欄位寫獨立驗證函式 |
使用 Composition API 把驗證邏輯抽成 useForm,在不同表單共用 |
| 即時搜尋 | 在 created 中發起 API,watch 監控搜尋字串 |
使用 ref + watchEffect,自動在字串變更時重新發送請求 |
| 全局狀態管理 | Vuex 3(依賴 Object.defineProperty) |
Vuex 4(與 Vue 3 完全相容)或 Pinia(基於 Proxy,開箱即支援 TypeScript) |
| 大型儀表板 | 多層嵌套的 <template>,大量 <div> 包裝 |
使用 Fragment 減少不必要的包裝,使用 Suspense 讓子圖表懶載入 |
| 跨層級彈窗 | 透過 $root 事件總線傳遞 |
使用 Teleport 把彈窗直接渲染到 body,避免層級限制 |
範例:使用 Teleport 建立全局 Modal
// Modal.vue
<template>
<teleport to="body">
<div class="overlay" v-if="visible" @click.self="close">
<div class="modal">
<slot />
</div>
</div>
</teleport>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const visible = ref(false)
const open = () => (visible.value = true)
const close = () => (visible.value = false)
defineExpose({ open, close })
</script>
<style scoped>
.overlay { /* 半透明背景 */ }
.modal { /* 中央對話框 */ }
</style>
// 在其他組件中使用
import Modal from './Modal.vue'
const modalRef = ref(null)
function showInfo() {
modalRef.value?.open()
}
總結
Vue 3 相較於 Vue 2 在 效能、可組合性、TypeScript 支援與生態系統 上都有顯著突破。核心差異包括:
- Composition API 取代或補足 Options API,使邏輯更易重用。
- Proxy 為基礎的響應式系統,提升偵測能力與效能。
- 全局 API 移至 app 實例,支援多應用同頁共存。
- 更完善的 TypeScript 整合,減少手動類型宣告。
- Fragment、Teleport、Suspense 等新特性,讓 UI 結構更彈性。
在實務開發中,建議先 從小模組開始練習 Composition API,逐步將大型組件遷移,並搭配 Pinia 或 Vuex 4 管理全局狀態。透過上述的差異說明與範例,你已具備從 Vue 2 平滑過渡至 Vue 3 的基礎知識,未來可以更自信地在新專案中運用 Vue 3 的強大功能,打造效能卓越且易於維護的前端應用。祝開發順利!