Vue3 課程 – 響應式系統(Reactivity System)
主題:ref vs reactive 差異
簡介
在 Vue 3 中,響應式系統 是框架的核心,也是開發者能夠以宣告式方式管理 UI 的根本。Vue 2 依賴 Object.defineProperty,而 Vue 3 則改用基於 Proxy 的全新實作,提供更好的效能與更彈性的 API。
其中最常見、最基礎的兩個 API 為 ref 與 reactive。雖然兩者都能讓資料「自動追蹤」與「自動更新」畫面,但它們的使用時機、行為與限制卻大不相同。掌握這兩者的差異,能讓你在開發時選擇最合適的工具,避免不必要的效能問題與 bug。
本篇文章將從概念、實作、常見陷阱與最佳實踐,逐步說明 ref 與 reactive 的差別,並提供 3–5 個實用範例,幫助你在真實專案中正確運用。
核心概念
1. ref:包裝原始值的「單一引用」
- 適用對象:基本類型(
string、number、boolean、null、undefined)或需要「單一」響應式容器的情況。 - 工作原理:
ref(value)會回傳一個擁有.value屬性的物件,Vue 會在讀取或寫入.value時自動收集依賴與觸發更新。 - 特性
- 自動解包:在模板 (
{{ }}) 或setup中使用ref時,Vue 會自動把.value解開,讓語法更直觀。 - 可與
reactive混用:ref內部的值若是物件,仍會保持原樣,不會自動轉成響應式(除非使用toRef、toRefs等輔助函式)。
- 自動解包:在模板 (
範例 1:基本類型的 ref
import { ref } from 'vue'
export default {
setup() {
const count = ref(0) // 包裝一個 number
const message = ref('Hello') // 包裝一個 string
function increment() {
count.value++ // 觸發更新
}
return { count, message, increment }
}
}
說明:
count.value++會觸發所有依賴count的組件重新渲染。模板中直接寫{{ count }}即可,Vue 會自動解包。
2. reactive:將整個物件「深層代理」為響應式
- 適用對象:物件、陣列、Map / Set(透過
reactive包裝)等結構。 - 工作原理:
reactive(obj)會回傳一個 Proxy,對該 Proxy 的任何屬性讀寫都會被追蹤。這是一個「深層」的代理,子屬性同樣具備響應式。 - 特性
- 原樣返回:返回的仍然是原物件的「代理」形式,使用時不需要
.value。 - 不可直接解包:若要在
setup中解構(const { a, b } = state),會失去響應式,必須使用toRefs或storeToRefs(Pinia)等方法。
- 原樣返回:返回的仍然是原物件的「代理」形式,使用時不需要
範例 2:物件的 reactive
import { reactive } from 'vue'
export default {
setup() {
const user = reactive({
name: 'Alice',
age: 25,
hobbies: ['reading', 'travel']
})
function birthday() {
user.age++ // 觸發更新,所有使用 user.age 的地方都會重新渲染
}
return { user, birthday }
}
}
說明:
user.age++會讓整個user代理保持同步,陣列hobbies內的變化(如push)同樣會被追蹤。
3. ref 與 reactive 的相互轉換
| 需求 | 建議 API |
|---|---|
把 reactive 物件的某個屬性單獨抽出成 ref |
toRef(state, 'prop') |
把 ref 包裝的物件轉成完整的 reactive |
reactive(ref.value)(注意:此時失去 .value 包裝) |
把 reactive 物件的所有屬性拆成 ref 集合 |
toRefs(state) |
範例 3:toRef 與 toRefs 的使用
import { reactive, toRef, toRefs } from 'vue'
export default {
setup() {
const form = reactive({
username: '',
password: ''
})
// 把單一屬性抽成 ref,方便在子元件中 v-model 使用
const usernameRef = toRef(form, 'username')
// 把所有屬性一次轉成 ref,常用於解構
const { username, password } = toRefs(form)
return { form, usernameRef, username, password }
}
}
說明:
usernameRef仍指向form.username,在子元件中v-model="usernameRef"能正確雙向綁定。若直接const { username, password } = form,則失去響應式。
4. 為什麼要區分 ref 與 reactive?
- 效能考量:
ref只代理一個單一值,開銷較小;reactive需要為整個物件建立 Proxy,若只需要追蹤單一屬性,使用ref會更輕量。 - 類型安全:在 TypeScript 中,
ref<number>會保留原始類型資訊,reactive則會把所有屬性視為可變。 - API 一致性:Vue 官方在 Composition API 中,推薦對「基本類型」使用
ref,對「複雜資料結構」使用reactive,保持程式碼語意清晰。
常見陷阱與最佳實踐
1. 解構會失去響應式
// ❌ 會失去響應式
const { name, age } = user // user 是 reactive 物件
解決方案:使用 toRefs 或 storeToRefs(Pinia):
const { name, age } = toRefs(user) // ✅ 仍保持響應式
2. ref 包裝的物件不會自動深層代理
const objRef = ref({ a: 1, b: { c: 2 } })
objRef.value.b.c = 3 // ❌ 不會觸發更新
解決方案:在需要深層代理時,直接使用 reactive:
const obj = reactive({ a: 1, b: { c: 2 } })
obj.b.c = 3 // ✅ 正常觸發
或使用 deep: true 的 watch 監聽整體變化(較少使用)。
3. 不要在 setup 之外直接使用 .value
// ❌ 在 template 之外直接操作
export const count = ref(0)
count.value++ // 可能在非 Vue 生命週期中執行,失去追蹤
最佳實踐:所有對 ref 的操作應放在組件的 setup、watch、computed 或 Vue 內建生命週期中。
4. 盡量避免在 reactive 中存放 ref
const state = reactive({
foo: ref(1) // ❌ 不必要的嵌套
})
原因:reactive 會自動把 ref 內部的值「解包」成普通屬性,導致混淆。若需要 ref,直接在 setup 中獨立宣告即可。
5. 使用 shallowRef 與 shallowReactive
shallowRef:只追蹤.value的變化,內部物件不會深層代理。適用於大型資料(如 API 回傳的 JSON)不需要頻繁監控子屬性。shallowReactive:只對第一層屬性建立 Proxy,子層保持原樣。
import { shallowRef } from 'vue'
const largeData = shallowRef(fetchLargeJson()) // 只在整體更換時觸發
實際應用場景
| 場景 | 建議使用 | 為什麼 |
|---|---|---|
| 表單欄位單獨綁定 | ref(或 toRef) |
每個欄位都是基本類型,使用 ref 可減少 Proxy 開銷 |
| 列表資料(陣列) | reactive(或 ref([])) |
陣列需要支援 push、splice 等方法,reactive 會自動追蹤 |
| 全局狀態管理(Pinia / Vuex) | reactive(或 ref + toRefs) |
整體狀態通常是物件結構,reactive 讓所有子屬性同步 |
| 大量不變資料(如圖表資料) | shallowRef |
只在整體更換時更新,避免每筆子屬性都觸發 re‑render |
| 在組件間傳遞單一值(如主題顏色) | ref |
輕量且可直接在多個組件中共享 |
| 需要動態新增屬性 | reactive |
Proxy 能捕捉新屬性的加入與刪除 |
總結
ref適合 包裝 單一、基本類型 或需要「單一引用」的情境;使用.value讀寫,Vue 會自動在模板中解包。reactive適合 包裝 物件、陣列、Map/Set 等結構,提供 深層代理,所有子屬性皆具備響應式。- 兩者可以相互轉換,但要注意 解構失效、深層代理 與 效能 的差異。
- 常見陷阱包括解構失去響應式、
ref包裝的物件不會自動深層代理、在非 Vue 生命週期內操作.value、以及不必要的ref嵌套。 - 在實務開發中,根據資料類型與效能需求選擇合適的 API,能讓程式碼更簡潔、效能更佳,也能降低維護成本。
掌握 ref 與 reactive 的差異與最佳實踐,將使你在 Vue 3 的響應式系統中游刃有餘,寫出 可讀、可維護、效能友好 的應用程式。祝開發順利!