本文 AI 產出,尚未審核

Vue3 課程 – 響應式系統(Reactivity System)

主題:ref vs reactive 差異


簡介

在 Vue 3 中,響應式系統 是框架的核心,也是開發者能夠以宣告式方式管理 UI 的根本。Vue 2 依賴 Object.defineProperty,而 Vue 3 則改用基於 Proxy 的全新實作,提供更好的效能與更彈性的 API。

其中最常見、最基礎的兩個 API 為 refreactive。雖然兩者都能讓資料「自動追蹤」與「自動更新」畫面,但它們的使用時機、行為與限制卻大不相同。掌握這兩者的差異,能讓你在開發時選擇最合適的工具,避免不必要的效能問題與 bug。

本篇文章將從概念、實作、常見陷阱與最佳實踐,逐步說明 refreactive 的差別,並提供 3–5 個實用範例,幫助你在真實專案中正確運用。


核心概念

1. ref:包裝原始值的「單一引用」

  • 適用對象:基本類型(stringnumberbooleannullundefined)或需要「單一」響應式容器的情況。
  • 工作原理ref(value) 會回傳一個擁有 .value 屬性的物件,Vue 會在讀取或寫入 .value 時自動收集依賴與觸發更新。
  • 特性
    • 自動解包:在模板 ({{ }}) 或 setup 中使用 ref 時,Vue 會自動把 .value 解開,讓語法更直觀。
    • 可與 reactive 混用ref 內部的值若是物件,仍會保持原樣,不會自動轉成響應式(除非使用 toReftoRefs 等輔助函式)。

範例 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),會失去響應式,必須使用 toRefsstoreToRefs(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. refreactive 的相互轉換

需求 建議 API
reactive 物件的某個屬性單獨抽出成 ref toRef(state, 'prop')
ref 包裝的物件轉成完整的 reactive reactive(ref.value)(注意:此時失去 .value 包裝)
reactive 物件的所有屬性拆成 ref 集合 toRefs(state)

範例 3:toReftoRefs 的使用

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. 為什麼要區分 refreactive

  1. 效能考量ref 只代理一個單一值,開銷較小;reactive 需要為整個物件建立 Proxy,若只需要追蹤單一屬性,使用 ref 會更輕量。
  2. 類型安全:在 TypeScript 中,ref<number> 會保留原始類型資訊,reactive 則會把所有屬性視為可變。
  3. API 一致性:Vue 官方在 Composition API 中,推薦對「基本類型」使用 ref,對「複雜資料結構」使用 reactive,保持程式碼語意清晰。

常見陷阱與最佳實踐

1. 解構會失去響應式

// ❌ 會失去響應式
const { name, age } = user   // user 是 reactive 物件

解決方案:使用 toRefsstoreToRefs(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: truewatch 監聽整體變化(較少使用)。

3. 不要在 setup 之外直接使用 .value

// ❌ 在 template 之外直接操作
export const count = ref(0)
count.value++   // 可能在非 Vue 生命週期中執行,失去追蹤

最佳實踐:所有對 ref 的操作應放在組件的 setupwatchcomputed 或 Vue 內建生命週期中。

4. 盡量避免在 reactive 中存放 ref

const state = reactive({
  foo: ref(1)   // ❌ 不必要的嵌套
})

原因reactive 會自動把 ref 內部的值「解包」成普通屬性,導致混淆。若需要 ref,直接在 setup 中獨立宣告即可。

5. 使用 shallowRefshallowReactive

  • shallowRef:只追蹤 .value 的變化,內部物件不會深層代理。適用於大型資料(如 API 回傳的 JSON)不需要頻繁監控子屬性。
  • shallowReactive:只對第一層屬性建立 Proxy,子層保持原樣。
import { shallowRef } from 'vue'

const largeData = shallowRef(fetchLargeJson()) // 只在整體更換時觸發

實際應用場景

場景 建議使用 為什麼
表單欄位單獨綁定 ref(或 toRef 每個欄位都是基本類型,使用 ref 可減少 Proxy 開銷
列表資料(陣列) reactive(或 ref([]) 陣列需要支援 pushsplice 等方法,reactive 會自動追蹤
全局狀態管理(Pinia / Vuex) reactive(或 ref + toRefs 整體狀態通常是物件結構,reactive 讓所有子屬性同步
大量不變資料(如圖表資料) shallowRef 只在整體更換時更新,避免每筆子屬性都觸發 re‑render
在組件間傳遞單一值(如主題顏色) ref 輕量且可直接在多個組件中共享
需要動態新增屬性 reactive Proxy 能捕捉新屬性的加入與刪除

總結

  • ref 適合 包裝 單一、基本類型 或需要「單一引用」的情境;使用 .value 讀寫,Vue 會自動在模板中解包。
  • reactive 適合 包裝 物件、陣列、Map/Set 等結構,提供 深層代理,所有子屬性皆具備響應式。
  • 兩者可以相互轉換,但要注意 解構失效深層代理效能 的差異。
  • 常見陷阱包括解構失去響應式、ref 包裝的物件不會自動深層代理、在非 Vue 生命週期內操作 .value、以及不必要的 ref 嵌套。
  • 在實務開發中,根據資料類型與效能需求選擇合適的 API,能讓程式碼更簡潔、效能更佳,也能降低維護成本。

掌握 refreactive 的差異與最佳實踐,將使你在 Vue 3 的響應式系統中游刃有餘,寫出 可讀、可維護、效能友好 的應用程式。祝開發順利!