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