2023/08/11 # Vue 的響應式基礎 Vue 是一個前端框架,它可以讓我們用簡單的方式開發出動態的網頁。學習使用 Vue 的一大重點就是它的**響應式**,也就是說當使用者在網頁改變了資料,網頁畫面也會自動更新。這篇文章將介紹什麼是響應式,以及如何使用 Vue 提供的 `ref()` 和 `reactive()` 函數來定義響應式的數據。 ## 什麼是響應式的值? 與一般數值的不同? 在 JavaScript 中,我們可以用各種類型的值來儲存資料,例如數字、字串、陣列、物件等。這些值都是**可變**的,也就是說,我們可以隨時修改它們的內容。例如: ```js let x = 1; // 定義一個數字 x = x + 1; // 修改數字 console.log(x); // 顯示 2 let y = [1, 2, 3]; // 定義一個陣列 y.push(4); // 修改陣列 console.log(y); // 顯示 [1, 2, 3, 4] ``` 但是,這些值並不是**響應式**的,也就是說,當我們修改了它們,並不會觸發任何其他的動作。例如,如果我們想要在網頁上顯示這些值,我們必須手動更新畫面。例如: ```html <div id="app"> <p>x = {{ x }}</p> <p>y = {{ y }}</p> </div> <script> let x = 1; let y = [1, 2, 3]; const app = Vue.createApp({ data() { return { x: x, y: y, }; }, }); app.mount("#app"); </script> ``` 這段程式碼會在網頁上顯示 `x = 1` 和 `y = [1, 2, 3]`。但是,如果我們在 console 中執行 `x = x + 1` 或 `y.push(4)`,網頁上的內容並不會改變。這是因為 `x` 和 `y` 只是普通的 JavaScript 值,它們並沒有和 Vue 的資料產生關聯。 要讓 Vue 的資料和畫面產生關聯,我們必須使用 Vue 提供的**響應式**的值。響應式的值和普通的值有以下幾點不同: - 響應式的值是由 Vue 內部管理的,它們有一些特殊的屬性和方法。 - 響應式的值可以被 Vue 的資料或其他響應式的值追蹤和監聽,當它們改變時,會觸發相關的更新。 - 響應式的值可以被 Vue 的模板或其他函數使用,當它們改變時,會自動更新畫面或執行其他動作。 ### 什麼情況會失去響應? 雖然 Vue 提供了方便的方式來創建和使用響應式的值,但是我們仍然要注意一些可能導致響應式失效的情況。以下是一些常見的例子: - 如果我們直接修改了一個物件或陣列的屬性或索引,而不是使用 Vue 提供的方法,則 Vue 可能無法偵測到變化。例如: ```js const obj = reactive({ a: 1 }); // 定義一個響應式的物件 obj.b = 2; // 直接新增一個屬性 console.log(obj.b); // 顯示 2 watch( () => obj.b, (newValue, oldValue) => { console.log(`obj.b changed from ${oldValue} to ${newValue}`); } ); // 定義一個監聽器 obj.b = 3; // 直接修改屬性 // 預期會顯示 obj.b changed from 2 to 3,但實際上沒有任何輸出 ``` 這是因為 Vue 在創建響應式的物件時,只會對已經存在的屬性進行追蹤,如果我們後來新增了新的屬性,Vue 就無法知道它的變化。要解決這個問題,我們可以使用 `Vue.set(obj, 'b', 2)` 或 `obj = { ...obj, b: 2 }` 來新增屬性,這樣 Vue 就可以正確地更新響應式。 - 如果我們使用了一些非響應式的值來定義響應式的值,則 Vue 只會追蹤響應式的值本身,而不會追蹤非響應式的值。例如: ```js let x = 1; // 定義一個非響應式的數字 const y = ref(x); // 定義一個響應式的數字 watch( () => y.value, (newValue, oldValue) => { console.log(`y changed from ${oldValue} to ${newValue}`); } ); // 定義一個監聽器 x = x + 1; // 修改非響應式的數字 // 預期會顯示 y changed from 1 to 2,但實際上沒有任何輸出 ``` 這是因為 Vue 在創建 `y` 時,只會把 `x` 的值複製過來,而不會建立任何關聯。如果我們後來修改了 `x` 的值,Vue 就無法知道它的變化。要解決這個問題,我們可以使用 `computed()` 函數來定義 `y`,這樣 Vue 就可以正確地更新響應式。例如: ```js let x = 1; // 定義一個非響應式的數字 const y = computed(() => x); // 定義一個響應式的數字 watch( () => y.value, (newValue, oldValue) => { console.log(`y changed from ${oldValue} to ${newValue}`); } ); // 定義一個監聽器 x = x + 1; // 修改非響應式的數字 // 正確地顯示 y changed from 1 to 2 ``` ## 創建響應式數值的函數: ref()、reactive() 要創建和使用響應式的值,Vue 提供了兩個主要的函數:`ref()` 和 `reactive()`。這兩個函數都可以把普通的值轉換成響應式的值,但是它們有一些不同的用法和特點,我們來看看它們的差異和適用的情況。 ### ref() `ref()` 函數可以把一個基本類型的值(例如數字、字串、布林等)轉換成一個響應式的物件,這個物件有一個 `value` 屬性,用來存放原始的值。例如: ```js const x = ref(1); // 定義一個響應式的數字 console.log(x.value); // 顯示 1 x.value = x.value + 1; // 修改數字 console.log(x.value); // 顯示 2 ``` ### reactive() `reactive()` 函數可以把一個複雜類型的值(例如物件、陣列、Map、Set等)轉換成一個響應式的物件,這個物件會保留原始的結構和方法,但是它的屬性或元素都會變成響應式的。例如: ```js const y = reactive([1, 2, 3]); // 定義一個響應式的陣列 console.log(y[0]); // 顯示 1 y[0] = y[0] + 1; // 修改陣列 console.log(y[0]); // 顯示 2 ``` ### 如何選擇使用? `ref()` 和 `reactive()` 都可以用來定義響應式的值,但是它們有一些不同的使用場景和注意事項。以下是一些常見的規則: - 如果我們要定義的值是一個基本類型,則建議使用 `ref()`,因為這樣可以避免在使用時忘記加上 `.value`。 - 如果我們要定義的值是一個複雜類型,則建議使用 `reactive()`,因為這樣可以保持原始的結構和方法,而不需要對每個屬性或元素都使用 `ref()`。 - 如果我們要定義的值是一個常數,則建議使用 `readonly()` 函數來包裝 `ref()` 或 `reactive()`,這樣可以避免意外地修改它們。 - 如果我們要定義的值是一個計算出來的值,則建議使用 `computed()` 函數來包裝 `ref()` 或 `reactive()`,這樣可以讓它們根據其他響應式的值自動更新。 ## 小結 Vue 的響應式是一個強大而方便的特性,它可以讓我們用簡單的方式開發出動態的網頁。在 Vue 中,我們可以使用 `ref()` 和 `reactive()` 函數來定義和使用響應式的值。這兩個函數都可以把普通的值轉換成響應式的值,但是它們有一些不同的用法和特點。我們要根據不同的情況選擇合適的函數,並且注意一些可能導致響應式失效的情況。這樣,我們就可以充分利用 Vue 的響應式,開發出高效而美觀的網頁。 ## 參考 - https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html - https://ithelp.ithome.com.tw/articles/10264271 - https://masteringjs.io/tutorials/vue/reactivity