在 Day 9 和 10 的文章中,我們提到 Vue 3 響應式基礎 - reactive & ref,主要用來攔截資料的讀取跟寫入,讓 Vue 去更新或執行依賴。
而 computed 和 watch 就是讓我們能在響應式數據被改變的時候,去更新加工的值或是執行其他程式碼。
computed 通常拿來做響應式資料的加工計算,具備緩存特性,能提昇效能,只在響應式資料改變時,重新執行運算。
const computedValue = computed(getterFunction);
const computedValue = computed({
// getter
get() {
//通常會回傳 reactive 資料加工後
return reactiveValue 的加工
},
// setter
set(newValue) {
//通常會將新寫入的值賦值給相關的 reactive 資料
reactiveValue = newValue
},
});
使用範例
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 使用解構賦值
[firstName.value, lastName.value] = newValue.split(' ')
}
})
fullName.value = 'Keanu Reeves'
Computed 在依賴的響應式資料變動時,會重新計算結果並回傳。
關於響應式資料的改變
「基本型別比較的是值,物件型別比較的是參照。」
基本型別被修改就會觸發,物件型別只在參照改變時觸發,單純修改原物件(新增屬性到物件、push 新項目到陣列),都不會觸發重新計算。
何為依賴的響應式資料?
由 ref 或 reactive 定義的響應式資料
需要透過 ref 物件或 reactive 物件進行讀取,才可以確實響應。
例如:將 ref 的 value
取出來存成區域變數,會失去響應性,value
變成一般純值。
下面的範例沒有響應性,改動 count.value 不會觸發 computed。
const counts = ref(0);
const value = counts.value;
// 沒有響應性,改動 count.value 不會觸發 computed
const computedCounts = computed(() => {
console.log("我在計算新的 computedCounts", value);
return `${value} 次`;
});
只要在 getter 執行時讀取到的響應式資料,都是 computed 的依賴,不是跟計算結果有依賴關係,才算是依賴。
下面的範例每次改變 counts.value 都會印出"嗨":
const computedCounts = computed(() => {
console.log("我在計算新的 computedCounts", counts.value);
return "嗨";
});
computed 是基於 Ref 物件進行擴展的,類別名稱是 ComputedRefImpl
,如果想要用一個 computed 結果,來計算另一個 computed,需要用 .value
取得 computed 的值。
const price = ref(1000);
const afterDiscount = computed(() => price.value * 0.9);
const reward = computed(() => afterDiscount.value * 0.1);
computed
屬性會將計算後的結果暫存起來,如果他使用到的屬性沒有更新,computed 就不會被重複執行。method
每次使用到都會呼叫。computed
不用呼叫,直接帶方法名稱method
要加()進行呼叫computed
不能帶參數method
可以帶參數Watcher 可以用來監視(響應式)狀態/資料的改變,並觸發 callback 函式。
watch(reactiveData, callback(newReactiveData, oldReactiveData))
newReactiveData
: 觸發 watcher
前的 reactiveData
oldReactiveData
: 觸發 watcher
後的 reactiveData
setup()
執行期間,同步進行註冊,如在異步期間進行註冊,無法綁定元件實例,需要自己手動清除。(好奇原因可以去看 Day22 的文章)watch()
函式的第一個參數要放想要監聽的資料來源,當然監聽的資料需要具有響應式,vue 才能觀察他的變化。
可以接受的資料包括:
接下來稍微說明不同資料類型的注意事項:
RefImpl
或 ComputedRefImpl
。RefImpl.value
或 ComputedRefImpl.value
,因為拿到的是純值watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
不管你的響應式資料是 ref、computed ref 或 reactvie object,重點是傳進入的 source:
.value
或內部屬性 obj.property
)為基本型別,都不能單獨監聽.value
或內部屬性 obj.property
)為物件型別,這個「內部物件」通常會是 Proxy 物件,是的話,就可以監聽。想要監聽響應式物件資料的情況下,可以選擇:
但這兩個方式是有差異的,監聽 reactive object 會監聽內部屬性的變化,但監聽回傳物件的 getter 時,預設監測的是參照(reference)是否改變。
範例
在 input 利用 v-model 綁定 reactive 物件內的物件,並嘗試分別寫了兩種 watcher。
<div>
<h4>reactive 物件中的物件</h4>
<input type="number" v-model="nestedReactiveObj.obj.input" />
<p>{{ nestedReactiveObj.obj.input }}</p>
</div>
const nestedReactiveObj = reactive({
name: "nestedReactiveObj",
obj: { name: "nestedAndNestedReactiveObj", input: "" },
});
watch(nestedReactiveObj, () => {
console.log("來自監測整個物件的 watcher");
});
watch(
() => nestedReactiveObj.obj,
() => {
console.log("來自監測 getter 的 watcher");
}
);
結果
在這種使用情境下(監聽 input 的數值去處發 callback),來自監測 getter 的 watcher 是不會被觸發的,他只會監測參照(reference)是否改變。
解決方法
將 watcher 的 {deep: true}
設定為 true,就可以監聽內部數據的改變,但要小心使用,深層監聽需要遍曆物件所有屬性,在資料很龐大的狀況下會影響效能。
很適合在 watchEffect 內發 API,取得資料做畫面渲染。
以 f2e 旅館預約 API 為例:
const rooms = ref([]);
watchEffect(async () => {
const response = await hotelAPI.GET("/rooms");
rooms.value = response.items;
console.log(rooms.value);
});
注意
watchEffect() 只追蹤同步執行期間相依賴的資料,一旦遇到 await,接下來的程式碼就被丟到 event loop,非同步時期才使用到的資料內容,都不會被追蹤。
computed 和 watcher 都可以根據響應式狀態的變化,去執行相對應的動作:
computed | watch | watchEffect |
---|---|---|
偵測一個或多個值 | 只能偵測一個值 | 偵測一個或多個值,非同步期間的響應式不會被追蹤 |
一開始會執行一次(有使用到的話) | 值變動時執行 | 一開始會執行一次 |
不能帶參數 | 參數一為新值、參數二為舊值 | 不能帶參數 |
只能處理同步 | callback 可以是同步或非同步 | callback 可以是同步或非同步 |
side effect 只能在 setter 觸發 | 可觸發 side effect | 可觸發 side effect |
回傳結果 | 執行 callback function | 執行 callback function |