Vue 3 基礎概念(Fundamentals)
Vue 是什麼、MVVM 架構原理
簡介
在前端開發的浪潮中,Vue.js 以其輕量、易上手、彈性高的特性,成為許多開發團隊的首選框架。Vue 3 更在效能、組件化以及 TypeScript 支援上大幅提升,讓開發者可以用更少的程式碼寫出更健全的應用。
了解 Vue 是什麼、以及它背後的 MVVM(Model‑View‑ViewModel) 架構,是學習任何前端框架的根本。掌握這些概念後,你才能在實務開發中正確設計資料流、避免不必要的重繪,並寫出易於維護的程式碼。
本文將從概念說明、程式碼範例、常見陷阱與最佳實踐,最後延伸到真實的應用場景,幫助初學者到中階開發者快速建立 Vue 3 的基礎認知。
核心概念
1. Vue 是什麼?
Vue 是一套 漸進式 的前端框架,核心只提供響應式資料綁定與組件系統,其餘功能(路由、狀態管理、測試等)則以插件形式提供。這樣的設計讓你可以:
- 只在單一頁面加入
<script src="https://unpkg.com/vue@3"></script>即可使用 - 在大型專案中使用 Vue CLI / Vite 建立完整的開發環境
2. MVVM 架構原理
| 層級 | 角色 | 主要職責 |
|---|---|---|
| Model | 資料層 | 負責儲存與管理應用的狀態(例如 API 回傳的 JSON) |
| View | UI 層 | 呈現使用者介面,直接綁定 Model 的資料 |
| ViewModel | 中介層 | 透過 雙向綁定(v-model)或 單向綁定({{ }})將 Model 與 View 連結起來,同時處理 UI 事件 |
在 Vue 中,ViewModel 就是 Vue 實例(或組件),它透過 reactivity 系統 監控 Model 的變化,當資料改變時自動觸發 View 的重新渲染,實現 資料驅動 UI。
3. 响應式系統(Reactivity)
Vue 3 使用 Proxy 取代 Vue 2 的 Object.defineProperty,使得:
- 嵌套物件、陣列 的變化都能被正確追蹤
- 性能 更佳,因為 Proxy 能一次捕捉所有屬性的讀寫操作
3.1 reactive 與 ref
reactive:適用於 物件,返回一個 Proxy 包裝的物件。ref:適用於 原始值(string、number、boolean)或需要解構的情況,返回一個帶有.value的引用。
import { reactive, ref, computed } from 'vue';
// reactive 範例:深層物件皆可追蹤
const user = reactive({
name: 'Alice',
age: 25,
address: {
city: 'Taipei',
zip: '100'
}
});
// ref 範例:單一值或需要解構的情況
const counter = ref(0);
// computed:基於 reactive / ref 計算衍生值
const greeting = computed(() => `Hello, ${user.name}!`);
4. 組件化(Component)
Vue 的組件是一個 可重用的 UI 單位,每個組件都有自己的 template、script、style。組件的生命週期、props、emit 等概念,使得 UI 可以 模組化、解耦合。
<!-- HelloWorld.vue -->
<template>
<h1>{{ msg }}</h1>
<button @click="count++">點擊次數:{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue';
defineProps({ msg: String });
const count = ref(0);
</script>
<style scoped>
h1 { color: #42b983; }
</style>
5. 單向 vs 雙向綁定
- 單向綁定(
{{ }}、v-bind):資料從 Model 流向 View,適合顯示性內容。 - 雙向綁定(
v-model):資料同時在 View 與 Model 之間同步,常用於表單輸入。
// 單向綁定範例
<div>{{ message }}</div>
// 雙向綁定範例(表單)
<input v-model="form.username" placeholder="請輸入帳號" />
程式碼範例
以下提供 5 個實用範例,說明 Vue 3 在不同情境下的使用方式。
範例 1:簡易計數器(使用 ref)
import { createApp, ref } from 'vue';
createApp({
setup() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
},
template: `
<button @click="increment">點擊 +1</button>
<p>目前計數:{{ count }}</p>
`
}).mount('#app');
說明:
ref讓原始值具備響應式,count.value的變化會自動更新畫面。
範例 2:使用 reactive 管理表單資料
import { createApp, reactive } from 'vue';
createApp({
setup() {
const form = reactive({
name: '',
email: ''
});
const submit = () => {
console.log('送出資料:', { ...form });
// 這裡可呼叫 API
};
return { form, submit };
},
template: `
<form @submit.prevent="submit">
<label>姓名:<input v-model="form.name" /></label><br/>
<label> Email:<input v-model="form.email" /></label><br/>
<button type="submit">送出</button>
</form>
`
}).mount('#app');
重點:
reactive讓整個物件保持響應式,v-model直接綁定子屬性。
範例 3:計算屬性(computed)與監聽(watch)
import { createApp, reactive, computed, watch } from 'vue';
createApp({
setup() {
const state = reactive({
price: 1200,
quantity: 2
});
const total = computed(() => state.price * state.quantity);
// 監聽 total 變化,示範副作用處理
watch(total, (newVal, oldVal) => {
console.log(`總金額從 ${oldVal} 變成 ${newVal}`);
});
return { state, total };
},
template: `
<div>
單價:<input type="number" v-model.number="state.price" /><br/>
數量:<input type="number" v-model.number="state.quantity" /><br/>
<strong>總金額:{{ total }}</strong>
</div>
`
}).mount('#app');
說明:
computed只在依賴變更時重新計算,watch用於執行副作用(如 API 呼叫、log)。
範例 4:父子組件溝通(Props + Emit)
<!-- Parent.vue -->
<template>
<Counter :initial="5" @update="handleUpdate" />
<p>子組件目前值:{{ childValue }}</p>
</template>
<script setup>
import { ref } from 'vue';
import Counter from './Counter.vue';
const childValue = ref(0);
const handleUpdate = (val) => {
childValue.value = val;
};
</script>
<!-- Counter.vue -->
<template>
<button @click="decrease">-</button>
{{ count }}
<button @click="increase">+</button>
</template>
<script setup>
import { ref, watch, defineProps, defineEmits } from 'vue';
const props = defineProps({ initial: Number });
const emit = defineEmits(['update']);
const count = ref(props.initial ?? 0);
const increase = () => count.value++;
const decrease = () => count.value--;
// 每次 count 改變時向父層發射事件
watch(count, (val) => emit('update', val));
</script>
重點:使用 props 接收父層資料,emit 向父層回傳變更,形成單向資料流。
範例 5:動態掛載組件(<component :is>)
import { createApp, ref, defineAsyncComponent } from 'vue';
const Home = { template: '<h2>首頁</h2>' };
const About = defineAsyncComponent(() => import('./About.vue'));
createApp({
setup() {
const current = ref('home'); // home | about
const switchView = (view) => current.value = view;
return { current, switchView };
},
template: `
<nav>
<button @click="switchView('home')">Home</button>
<button @click="switchView('about')">About</button>
</nav>
<component :is="current === 'home' ? Home : About"></component>
`,
components: { Home, About }
}).mount('#app');
說明:
<component :is>可以根據狀態切換不同組件,配合 lazy loading(defineAsyncComponent)提升首屏載入速度。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 建議的解決方案 |
|---|---|---|
| 直接修改 Props | Vue 警告「Attempting to mutate a prop directly」並破壞單向資料流 | 使用 本地副本(const local = ref(props.xxx))或 emit 事件回傳變更 |
非響應式的物件屬性(例如直接 obj.newProp = 1) |
新屬性不會觸發更新 | 使用 reactive 時,set 新屬性前先 obj = {...obj, newProp: 1},或使用 Vue.set(Vue 2)/ reactive 的 proxy 自動偵測 |
在 setup 中使用 this |
this 為 undefined,導致錯誤 |
直接使用返回的變數或 context,不要依賴 this |
| 大量資料一次性寫入(如大量陣列 push) | 多次重繪影響效能 | 使用 批次更新(nextTick)或 splice 替代多次 push |
忘記在模板中使用 .value(使用 ref) |
顯示為 [object Object] 或空值 |
在模板中 自動解包,但在 script 中需使用 .value |
最佳實踐
- 盡量使用 Composition API (
setup):邏輯更易拆分、重複使用。 - 保持單向資料流:父層提供
props,子層只透過emit回傳。 - 利用
computed取代不必要的watch:計算屬性是更簡潔的衍生資料寫法。 - 懶載入大型組件:
defineAsyncComponent配合路由分割點,減少首屏載入時間。 - 使用 TypeScript:Vue 3 原生支援,能在開發階段捕捉錯誤,提升可維護性。
實際應用場景
| 場景 | 為何選擇 Vue 3 | 典型實作方式 |
|---|---|---|
| 企業內部儀表板 | 需要 即時更新、大量圖表、表單驗證 | 使用 reactive 管理全域狀態,computed 計算統計值,watch 監聽 API 回傳 |
| 行動電商前端 | SEO、SSR(Nuxt)與 SPA 兼容 | 在 Nuxt 3 中使用 Vue 3 的 Composition API,搭配 pinia 做全域購物車 |
| 中小型 SaaS 產品 | 快速開發、組件庫(如 Element Plus) | 建立可重用的 UI 組件,利用 props + emit 組成表單套件 |
| 單頁式管理系統 | 路由分割、權限控管 | 使用 Vue Router + defineAsyncComponent 動態載入功能模組,配合 navigation guards 進行權限檢查 |
| 即時聊天應用 | 雙向綁定、WebSocket 整合 | ref 保存訊息列表,watchEffect 監聽 WebSocket 推送,computed 計算未讀數量 |
總結
Vue 3 以 Proxy 為核心的響應式系統、Composition API 以及 組件化 為基礎,完美落實 MVVM 的資料驅動理念。掌握 reactive、ref、computed、watch 以及 props / emit 的正確使用方式,能讓你在開發過程中:
- 減少手動 DOM 操作,讓 UI 自動跟隨資料變化
- 提升可維護性:邏輯分離、組件重用、單向資料流
- 優化效能:懶載入、批次更新、最小化重繪
在實務上,從簡易計數器到企業級儀表板,Vue 3 都能提供彈性且高效的開發體驗。只要遵循 最佳實踐,避開常見陷阱,你就能在短時間內構建出 可擴展、易維護 的前端應用。
祝你在 Vue 3 的旅程中收穫滿滿,寫出更快、更好、更有品質的程式碼! 🚀