# Watch 的情境使用



watch 總共有三個參數

1. `val` : 第一個為 被監聽的變數
2. `newVal` : 當監聽數值變化時，產生的新數值
3. `oldVal` : 當監聽數值變化時，產生的舊數值

```javaScript
watch(val, (newVal, oldVal) => {
    console.log(`new: ${newVal} ; old: ${oldVal}`);
});
```

## ref & reactive watch 監控差異

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

```javascript
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 的話，我們先用上方的邏輯來嘗試撰寫一次

```javaScript
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 警告](https://i.imgur.com/HaL44jY.png "warn 警告")

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

```javaScript
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 的話，是無法被成功監聽的

```javaScript
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 整個物件的變動，**如果僅需要觀察物件為一個數值的話**，可以使用下方的寫法：

```javaScript
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 物件型別

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

```javaScript
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 函示即可，並且在讀取或是執行需要觀察的數值，即可自動加入監聽該數值，例如下方這段基礎的起手式：

```javaScript
const num = ref(0);

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

:::tip
`watchEffect` 與 `watch` 最大的差異為：

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

:::

## `stop()`

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

```javaScript
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。

