LoginSignup
35
11

More than 3 years have passed since last update.

CompositionAPIのwatchとwatchEffectの違い

Posted at

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

Composition API v0.5.0で追加されたwatchEffect関数をご存知でしょうか。
実はもう2ヶ月ほど前に追加されてますが私はつい先日触ったのでここに書いておこうと思います。

Vue v2のwatch

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

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

vue.v2
computed: {
  fullName: function () {
    return this.firstName + ' ' + this.lastName
  }
}

なので、watchを使う場合は何らかの変数の変更を検知して他の処理を行いたい場合に使うことが多く、このように別の変数に入れ直す場合はcomputed、算出プロパティを使うのが良いと思います。
この辺は公式ページで丁寧に説明されてるのでおさらい程度で。

Vue v3のwatch

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

vue.v3
setup() {
  const hoge = ref('')
  watch(
    () => hoge,
    (hoge) => {
      somethingMethod(hoge)
    }
  )
}

第一引数に監視対象、第二引数に変更を検出した際に実行したい関数を定義できます。
ほとんどv2と変わらない感じですね。

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

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

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

// 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を返り値としているので以下のように監視を停止することができます。

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

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

setup() {
  const hoge = reactive({
    fuga: {
      piyo: true,
      hogera: false,
    },
    hogehoge: false
  })
  watch(
    () => hoge,
    () => {},
    { deep: true }
  )
}

この場合、watch関数の第三引数、WatchOptionsdeep: trueと指定することで実現できます。

watchEffect

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

setup() {
  const hoge = ref('')
  watchEffect(() => {
    somethingMethod(hoge.value)
  })
}

watchに比べると監視対象を明示的に宣言していないので簡略して書けますが、何がトリガーで実行されてるかがわかりにくいという反面もあります。
また、複数のオブザーバブルな値が入る場合には注意が必要で

setup() {
  const hoge = ref('')
  const fuga = ref('')
  watchEffect(() => {
    fuga.value = somethingMethod(hoge.value)
  })
}

このように別のリアクティブ変数に入れ直す処理を書いた場合に、fuga変数の変更も監視されてwatchEffectが実行されるのでおそらく無限ループされます。
こういう場合はfugacomputedにするか、watchを使うと良いと思います。

型定義はこちら

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楽しみですね。

35
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
11