Watch 的情境使用

watch 總共有三個參數

  1. val : 第一個為 被監聽的變數
  2. newVal : 當監聽數值變化時,產生的新數值
  3. oldVal : 當監聽數值變化時,產生的舊數值
watch(val, (newVal, oldVal) => {
    console.log(`new: ${newVal} ; old: ${oldVal}`);
});

ref & reactive watch 監控差異

下方範例測試簡單的 ref watch 範例來執行:

const { ref, reactive, watch } = Vue;
const App = {
  setup() {
    const num = ref(0);

    watch(num, (n, o) => {
      console.log(`new: ${n} ; old: ${o}`);
    });
    setInterval(() => {
      num.value++;
    }, 2000);

    return {
      num,
    };
  },
};

Vue.createApp(App).mount("#app");

而當今天如果要改為 reactive 的話,我們先用上方的邏輯來嘗試撰寫一次

const { ref, reactive, watch } = Vue;
const App = {
  setup() {
    const num1 = reactive({ idx: 0 });

    watch(num1, (n, o) => {
      console.log(`new: ${n} ; old: ${o}`);
    });
    setInterval(() => {
      num1.idx++;
    }, 2000);

    return {
      num1,
    };
  },
};

Vue.createApp(App).mount("#app");

結果會發現沒有任何 console 產生,並且在 devtool 新增了一個 warning 警告提示

![warn 警告](i.imgur.com/HaL44jY.png "warn 警告")

:::tip warn 警告提示意思是指 我現在 watch 能監控的數值,它只能被 getter,也就是只能被讀取的數值,所以需要透過一個函示,去將 reactive 的 key 數值,return 出來讓數值可以去 setter,下方為更改過後的程式碼: :::

const { ref, reactive, watch } = Vue;
const App = {
  setup() {
    const num1 = reactive({ idx: 0 });

    watch(
      () => num1.idx,
      (n, o) => {
        console.log(`new: ${n} ; old: ${o}`);
      }
    );
    setInterval(() => {
      num1.idx++;
    }, 2000);

    return {
      num1,
    };
  },
};

Vue.createApp(App).mount("#app");

ref & reactive 物件監聽差異

當今天 ref & reactive 兩者都為物件型別,在 watch 時,會發生一些不同。

ref 物件型別

是無法直接進行深層監聽的,需要使用其他的方法來處理, 也就是當今天 ref 裡頭有個 index:0 的數值,如果我只有觀察 num 整個 ref 的話,是無法被成功監聽的

const { ref, reactive, watch } = Vue;

const App = {
  setup() {
    const num = ref({ idx: 0 });

    watch(num, (n, o) => {
      console.log(`new: ${n} ; old: ${o}`);
    });

    setTimeout(() => {
      num.value.idx++;
    }, 2000);

    return {
      num,
    };
  },
};

Vue.createApp(App).mount("#app");

ref 其實是可以觀察物件型別的,只是不能監控 ref 整個物件的變動,如果僅需要觀察物件為一個數值的話,可以使用下方的寫法:

const { ref, reactive, watch } = Vue;

const App = {
  setup() {
    const refObj = ref({ idx: 0 });

    watch(
      ()=> refObj.value.idx,
      (n, o) => {
      console.log(`new: ${n} ; old: ${o}`);
    });

    setTimeout(() => {
      refObj.value.idx++;
    }, 2000);

    return {
      refObj,
    };
  },
};

Vue.createApp(App).mount("#app");

如果真的必須得監控整個 ref 物件時,可以新增 deep:true 的屬性設定,但千萬要記得盡可能避免此寫法,因為瀏覽器是需要耗費較多的效能,去持續一個一個監聽數值被改變過後的樣子。

reactive 物件型別

如果為物件型別,可以透過函示去將觀察的數值回傳出來即可,成功解決

const { ref, reactive, watch } = Vue;

const App = {
  setup() {
    const num = ref({ idx: 0 });
    const num1 = reactive({ idx: 0 });

    watch(num, (n, o) => {
      console.log(`new: ${n} ; old: ${o}`);
    });
    watch(
      () => num1.idx,
      (n, o) => {
        console.log(`new: ${n} ; old: ${o}`);
      }
    );

    setTimeout(() => {
      num.value.idx++;
      num1.idx++;
    }, 2000);

    return {
      num,
      num1,
    };
  },
};

Vue.createApp(App).mount("#app");

watchEffect

跟 watch 方法一樣,是在監聽數值的變更,只是有一些不同的使用方式以及特性

首先,watchEffect 是不需要傳入任何所需要監控的數值,直接使用 callback 函示即可,並且在讀取或是執行需要觀察的數值,即可自動加入監聽該數值,例如下方這段基礎的起手式:

const num = ref(0);

watchEffect(() => {
  console.log(num.value)
});

:::tip watchEffectwatch 最大的差異為:

  • watch 當所監控數值有變化時,才會去觸發。
  • watchEffect 則是畫面在 mounted 完成時,就會先執行一次。

:::

stop()

當今天如果有需要監聽事件,在變化到一個數值,停止監聽時,可以使用 stop(),這個 stop 是可以自定義名稱的,但建議用比較直覺性的名稱來表示,例如我這裡使用的名稱為 stop

const num = ref(0)

const stop = watchEffect(() => {
  if (num.value >= 4) {
    stop();
  }
});

setInterval(() => {
  num.value++;
}, 1000);

:::warning 這邊也再次強調,雖然我們透過 devtool 可以發現在 console 只有到 4 就沒有再繼續 console 了, 但實際上 stop() 只是關閉了整個 watchEffect() ,範例中下方的 setInterval() 依然在持續進行的! :::

結論

ref 以及 reactive 在 watch 的使用上會稍微的不一樣,所以要去看是否有無要 watch 數值,去選擇要使用 ref or reactive。