CompositionAPIのwatchとwatchEffectの違い

みなさん、Vue v3(vue-next)使ってますか？私は使ってません。
Vue v2にComposition Apiパッケージを導入して雰囲気を楽しんでいます。

Composition API v0.5.0で追加された[watchEffect](https://vue-composition-api-rfc.netlify.app/api.html#watcheffect)関数をご存知でしょうか。
実はもう２ヶ月ほど前に追加されてますが私はつい先日触ったのでここに書いておこうと思います。


## Vue v2のwatch

```js:vue.v2
watch: {
  firstName: function (val) {
    this.fullName = val + ' ' + this.lastName
  },
  lastName: function (val) {
    this.fullName = this.firstName + ' ' + val
  }
}
```

皆さんに馴染みがあるのはこのwatchではないでしょうか。
`firstName`と`lastName`の変数を監視して、変更があった場合に別の変数を更新する。
しかし、この場合は公式ページでも書かれているように`computed`を使ったほうが良いでしょう。

```js:vue.v2
computed: {
  fullName: function () {
    return this.firstName + ' ' + this.lastName
  }
}
```
なので、`watch`を使う場合は何らかの変数の変更を検知して他の処理を行いたい場合に使うことが多く、このように別の変数に入れ直す場合は`computed`、算出プロパティを使うのが良いと思います。
この辺は公式ページで丁寧に説明されてるのでおさらい程度で。

## Vue v3のwatch
Vue v3もとい、CompoisitionAPI(v0.5.0以降)は`watch`と`watchEffect`の２つのウォッチャーを提供しています。



```js:vue.v3
setup() {
  const hoge = ref('')
  watch(
    () => hoge,
    (hoge) => {
      somethingMethod(hoge)
    }
  )
}
```
第一引数に監視対象、第二引数に変更を検出した際に実行したい関数を定義できます。
ほとんどv2と変わらない感じですね。

複数の監視対象にも対応していて次のように配列で渡すこともできます。

```ts
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
```

もうちょっと詳細に型定義を見てみると

```ts
// wacthing single source
function watch<T>(
  source: WatcherSource<T>,
  callback: (
    value: T,
    oldValue: T,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options?: WatchOptions
): StopHandle

// watching multiple sources
function watch<T extends WatcherSource<unknown>[]>(
  sources: T
  callback: (
    values: MapSources<T>,
    oldValues: MapSources<T>,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options? : WatchOptions
): StopHandle

type WatcherSource<T> = Ref<T> | (() => T)

type MapSources<T> = {
  [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}

// see `watchEffect` typing for shared options
interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // default: false
  deep?: boolean
}
```

`StopHandle`を返り値としているので以下のように監視を停止することができます。

```ts
setup() {
  const hoge = ref('')
  const stop = watch(
    () => hoge,
    (hoge) => {
      somethingMethod(hoge)
    }
  )
  stop()
}
```

監視対象のオブジェクトが階層化していて、そのすべての変更検出したい場合もあるかと思います。

```ts
setup() {
  const hoge = reactive({
    fuga: {
      piyo: true,
      hogera: false,
    },
    hogehoge: false
  })
  watch(
    () => hoge,
    () => {},
    { deep: true }
  )
}
```
この場合、watch関数の第三引数、`WatchOptions`で`deep: true`と指定することで実現できます。

## watchEffect
Composition API v0.5.0で追加された`watchEffect`は`watch`とは異なり、`callback`内部にあるオブザーバブル値を検出して実行されます。

```ts
setup() {
  const hoge = ref('')
  watchEffect(() => {
    somethingMethod(hoge.value)
  })
}
```
watchに比べると監視対象を明示的に宣言していないので簡略して書けますが、何がトリガーで実行されてるかがわかりにくいという反面もあります。
また、複数のオブザーバブルな値が入る場合には注意が必要で

```ts
setup() {
  const hoge = ref('')
  const fuga = ref('')
  watchEffect(() => {
    fuga.value = somethingMethod(hoge.value)
  })
}
```
このように別のリアクティブ変数に入れ直す処理を書いた場合に、`fuga`変数の変更も監視されて`watchEffect`が実行されるのでおそらく無限ループされます。
こういう場合は`fuga`を`computed`にするか、`watch`を使うと良いと思います。

型定義はこちら

```ts
function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

type InvalidateCbRegistrator = (invalidate: () => void) => void

type StopHandle = () => void
```

より詳細な情報は公式を見てください。
Vue v3楽しみですね。