Vue3 - 關於計算屬性與監控資料

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

cover image


回到 手把手開始寫 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 其實還有第二個參數。


computedsetter

第一個參數預設是 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.valuefirstName.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 )

題外話: 其實這給我的感覺有點像 ReactuseEffect()

直接看範例:

假設我有一個函數 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




最後更新時間: 2022年07月16日.