Skip to content

Vue 3 核心教學:掌握響應式基礎與資料驅動核心

Vue 3 響應式基礎:ref 與 reactive

建立應用程式 時,我們反覆提到了一個詞:資料驅動畫面。當 JavaScript 中的資料改變時,HTML 畫面就會魔術般地自動更新。

這個魔術在 Vue 3 中,主要是透過 Composition API 的 ref()reactive() 兩個核心方法來實現的。本文將帶您深入這兩個方法的底層邏輯,讓您不再為 .value 的使用時機感到困惑,並能正確選用適合的響應式武器。

為什麼需要響應式?直擊 Vue 3 的底層秘密

在傳統的 JavaScript 中,如果您修改了一個變數,瀏覽器是不會主動幫您更新網頁畫面的。您必須自己動手寫 document.getElementById().innerText = newValue

Vue 3 則是利用了 ES6 的 Proxy(代理) 機制。當您把一個普通的資料丟進 Vue 的響應式系統時,Vue 會在外面包一層 監聽器。只要您對資料進行讀取或修改,監聽器就會立刻察覺,並通知網頁去重新渲染對應的區塊。

為了滿足不同資料型態的監聽需求,Vue 3 提供了兩種主要方式:ref()reactive()

單一值的守護者:ref

ref() 是 Vue 3 最常用且最萬能的響應式宣告方式。

適用資料型態

ref() 可以接收任何資料型態,包含:

  • 基本型態:String, Number, Boolean, null, undefined。
  • 複合型態:Object, Array。

核心特點:value 的語法糖

因為 JavaScript 的基本型態,例如:數字或字串是傳值而非傳參照,Vue 無法直接監聽它們的變化。因此,ref() 會將您傳入的值包裝成一個 特殊的物件 RefImpl,並把真實的資料存放在 value 屬性中。

在 JavaScript

必須加上 .value 才能讀取或修改資料。

js
const price = ref(100);
const isVisible = ref(false);
const username = ref('');

// 數字運算
price.value = price.value * 0.9; // 打九折

// 布林切換
isVisible.value = !isVisible.value;

// 字串操作
username.value = username.value.trim().toUpperCase();

在 HTML 模板

Vue 會自動幫您 解包(Unwrap),因此絕對不要加 .value

html
<!-- 模板中不加 .value -->
<p>{{ count }}</p>
<p>{{ name }}</p>

物件與陣列的專屬管家:reactive

相較於 ref() 的萬能,reactive() 則是專門用來處理非基本型態(物件或陣列)的響應式方法。

適用資料型態

只能接收物件或陣列。如果您嘗試把數字或字串丟進 reactive(),Vue 會在控制台跳出警告。

核心特點:免用 .value

因為 reactive() 直接將物件透過 Proxy 進行深度代理,所以不論在 JavaScript 還是 HTML 模板中,您都完全不需要寫 .value,操作起來就跟原生 JavaScript 物件一模一樣。

陣列操作

所有原生陣列方法都可以直接用,且都會觸發反應式更新:

js
const items = reactive(['html', 'css', 'javascript']);

items.push('vue'); // 尾端新增
items.unshift('http'); // 頭端新增
items.pop(); // 移除最後一個
items.shift(); // 移除第一個
items.splice(1, 1); // 移除索引 1 的元素
items[0] = 'HTML5'; // 直接修改索引(Vue 3 支援)
items.sort(); // 排序
items.reverse(); // 反轉

物件操作

js
const user = reactive({
  name: '小明',
  age: 18,
  email: 'ming@example.com',
});

// 讀取
console.log(user.name); // '小明'
console.log(user.age); // 18

// 修改屬性
user.name = '小華';
user.age++;

// 新增屬性(Vue 3 支援,Vue 2 不支援)
user.phone = '0912-345-678';

開發大抉擇:ref 與 reactive 的關鍵對比與地雷

既然兩者都能處理物件,開發時該怎麼選?以下是初學者必知的三大核心差異與隱藏地雷:

核心特性快速對比表

特性refreactive
可接受型態任何型態(基本與複合型態皆可)僅限物件、陣列、Map/Set
JS 中讀寫必須使用 .value直接讀寫,免用 .value
HTML 模板自動解包,直接使用直接使用
底層機制包裝成物件 RefImpl透過 ES6 Proxy 代理

reactive 的致命地雷:失去響應性

雖然 reactive() 少了 .value 看起來很優雅,但它隱藏了兩個非常容易讓網頁畫面卡死更新的地雷:

重新賦值

如果您直接把一個新物件指派給 reactive 變數,舊的 Proxy 參照會被切斷,Vue 將徹底失去對它的追蹤!

js
let state = reactive({ count: 0 });

// 這樣做會讓 state 失去響應性!畫面再也不會變了
state = { count: 99 };

若需要重新賦值,用 Object.assign()

js
const user = reactive({ name: '小明' });

Object.assign(user, { name: '小華', age: 20 });

解構賦值

直接解構 reactive 物件,拿出來的變數會變成普通的純值,同樣失去響應性。

js
const state = reactive({ count: 0 });
// 拿出來的 count 只是個普通的數字 0,修改它不會觸發畫面更新
let { count } = state;

若需要解構,用 toRefs()

js
const { reactive, toRefs } = Vue;
const user = reactive({ name: '小明', age: 18 });
const { name } = user; // 解構後 name 失去響應式

// 若需要解構,用 toRefs
const { name } = toRefs(user);

如何避開地雷?

  • 如果用 reactive 想重新賦值,必須改成修改其內部屬性,例如:state.data = newData
  • 如果使用 ref 處理物件,例如:const state = ref({ count: 0 }),重新賦值只需要 state.value = { count: 99 },完全不會有失去響應性的風險!

常見問題

ref 與 reactive 該選哪一個?

實務上建議一律使用 ref。它可以接受任何型態,重新賦值也不會失去響應性;reactive 只能放物件或陣列,且有重新賦值與解構的地雷。

什麼時候要加 .value?

<script> 中讀寫 ref 一律要加 .value;在 <template> 中 Vue 會自動解包,直接寫變數名稱即可。

為什麼 reactive 物件重新賦值後畫面就不動了?

因為重新賦值會讓變數指向一個全新的普通物件,原本的 Proxy 參照被切斷,Vue 就追蹤不到了。要整批更新請改用 Object.assign(),或一開始就用 ref

解構 reactive 物件為什麼會失去響應性?

解構會把屬性的當下值複製成普通變數,與原本的 Proxy 再無關聯。需要解構時請先用 toRefs() 包一層,解構出來的每個屬性才會是 ref

ref 裡放物件時是深層響應嗎?

是。ref 接到物件時,內部會用 reactive 做深層代理,因此修改巢狀屬性一樣會觸發畫面更新。若只需要淺層追蹤,可以改用 shallowRef

為什麼直接用 index 修改陣列在 Vue 3 有效,Vue 2 卻無效?

Vue 2 以 Object.defineProperty 攔截屬性,無法偵測索引賦值與新增屬性;Vue 3 改用 ES6 Proxy 代理整個物件,索引賦值、新增與刪除屬性都能被追蹤。

延伸閱讀