Vue3 - 關於計算屬性與監控資料
tags: Vue
category: Front-End
description: Vue3 - 關於計算屬性與監控資料
created_at: 2022/07/16 13:00:00

回到 手把手開始寫 Vue3
前言
還是假設你已經做好了第一篇做的事 開新專案與環境設定
計算屬性 (Computed)
基本使用方式
有時候會有一種需求,例如像是下面這樣
<script setup>
import { ref } from "vue";
const firstName = ref("junbin");
const lastName = ref("lai");
</script>
<template>full name: {{ lastName }} {{ firstName }}</template>
這邊你會看到畫面上印出
full name: lai junbin
然後你可以寫成這樣
<script setup>
import { computed, ref } from "vue";
const firstName = ref("junbin");
const lastName = ref("lai");
const fullName = computed(() => {
return `${lastName.value} ${firstName.value}`;
});
</script>
<template>full name: {{ fullName }}</template>
這樣的話不管是 firstName 還是 lastName 有改變,那個 fullName 都會跟著變動。
那你可能會有個疑問,那幹嘛不使用 function 去組起來就好,功能一樣啊!
<script setup>
import { ref } from "vue";
const firstName = ref("junbin");
const lastName = ref("lai");
const fullName = () => {
return `${lastName.value} ${firstName.value}`;
};
</script>
<template>full name: {{ fullName() }}</template>
但是如果使用函數的話,就吃不到 cache 的好處,如果你不需要 cache,當然可以使用函數去做。
而 computed 只有在相依的狀態改變,才會做改變。
而 computed 其實還有第二個參數。
computed 的 setter
第一個參數預設是 getter ,如果要宣告 setter 就要丟進一個物件,像是這樣
<script setup>
import { computed, ref } from "vue";
const firstName = ref("junbin");
const lastName = ref("lai");
const fullName = computed({
get() {
return `${lastName.value} ${firstName.value}`;
},
set(newValue) {
[lastName.value, firstName.value] = newValue.split(" ");
},
});
setTimeout(() => {
fullName.value = "Doe John";
}, 1000);
</script>
<template>full name: {{ fullName }}</template>
然後你會發現在 1 秒後 fullName.value 被修改,而其實是 lastName.value 與 firstName.value 被修改,導致 fullName 的值被替換了
關於監控 (watch)
上面提到的 computed 其實也有一點監控的感覺(?),所以就把他放在一起寫了。
有時候可能有個需求是要去監控特定狀態有沒有改變,變了之後要在做特定的處理,就會用到這個語法 watch
基本用法 (監測響應式資料)
<script setup>
import { ref, watch } from "vue";
const count = ref(0);
watch(count, () => {
console.log("count 變了!!");
});
setTimeout(() => {
count.value = 100;
}, 1000);
</script>
<template>
<div>count: {{ count }}</div>
</template>
上面這個範例在 1 秒後 count 的值會改變,就會觸發那個 console.log()。
而後面帶的 callback 函數實際上是有傳入值的,分別是新、舊的值,像是這樣
watch(count, (newValue, oldValue) => {
console.log(`count 從 ${oldValue} 變成 ${newValue}`);
});
會看到輸出 count 從 0 變成 100,這一段訊息。
監測 getter
第一個參數也可以監聽一個 getter 函數,只要函數的產出值有改變,就會觸發後面的 callback
import { ref, watch } from "vue";
const x = ref(5);
const y = ref(10);
watch(
() => x.value + y.value,
(newValue, oldValue) => {
console.log(oldValue, newValue);
}
);
setTimeout(() => {
x.value = 100;
}, 1000);
像是上面這個範例會輸出: 15 110,因為他從 5 + 10 變成了 100 + 10。
監測陣列
也可以監聽一個陣列,像是這樣
watch([x, () => x.value + y.value], (newValue, oldValue) => {
console.log(oldValue, newValue);
});
那他會輸出
(2) [5, 15] (2) [100, 110]
監測物件
watch 也可以監聽物件
如果是監聽特定 key 的話要使用 getter 的形式,而不是使用下面這段。
import { reactive, watch } from "vue";
const obj = reactive({ count: 0 });
watch(obj.count, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
setTimeout(() => {
obj.count = 100;
}, 1000);
應該寫成
watch(
() => obj.count,
(newValue, oldValue) => {
console.log(oldValue, newValue);
}
);
而如果要監聽整個物件的話,有兩個方式:
第一個方式是直接丟進一個物件讓他監聽,他就會深度去監聽
import { reactive, watch } from "vue";
const obj = reactive({
user: {
name: "user01",
},
});
watch(obj.user, (newValue, oldValue) => {
console.log(oldValue, newValue);
});
setTimeout(() => {
obj.user.name = "user02";
}, 1000);
第二個方式是丟進 getter 函數,然後加上一個 { deep: true} 的 options
watch(
() => obj.user,
(newValue, oldValue) => {
console.log(oldValue, newValue);
},
{
deep: true,
}
);
然後不管是哪個方式,都要注意一件事,就是他的新舊值都會一樣,因為都是取到同一個物件。
大概會長得像這樣
Proxy {name: 'user02'} Proxy {name: 'user02'}
注意:最後是因為深度監控需要去迭代物件中的每一個屬性,所以如果物件很肥可能會有效能問題,要注意。
watchEffect()
有時候可能有一種需求,需要在一開始初始化的時候做某件事,然後要是某些狀態發生改變之後再做一次。
例如一開始先打一次 API 拿到資料,要是有什麼東西變了,要再重打一次之類的(例如 page )
題外話: 其實這給我的感覺有點像 React 的 useEffect()。
直接看範例:
假設我有一個函數 f() 會讓 data 設定成 n 的兩倍 (假設這是一個打 API 的函數,會根據 n(假設這是頁數)的值去得到不同的 data)
<script setup>
import { ref, watch } from "vue";
const data = ref(0);
const n = ref(1);
const f = () => {
data.value = n.value * 2;
};
f();
watch(n, f);
setTimeout(() => {
n.value = 2;
}, 1000);
</script>
<template>
<div>{{ data }}</div>
</template>
那每一次都必須先手動打一次 f() 之後再監聽要是特定狀態有變化,再去重新呼叫一次函數。
要精簡這樣的操作,就可以使用 watchEffect()
watchEffect(() => {
data.value = n.value * 2;
});
這樣的話當 n.value 有改變,他就會自動再執行一次 callback 當中的內容,進而更新 data