thumbnail

【Vue3】watch系関数の違いと使い方(watchSyncEffect・watchPostEffect含)

Vue3.2の時点でwatch系の関数はwatch / watchEffect / watchPostEffect / watchSyncEffectの4種類あります。
それぞれの意味と違いをご存知でしょうか。今回はこの4種類のwatchについて解説していきます。
また、複数の値を監視するwatchやネストされた値の監視、watchの実行タイミングについても解説しています。

watch関数の種類

watch系の関数は4種類ある

watch系の関数は全部で以下の4種類存在します。

  • ① watch
  • ② watchEffect
  • ③ watchPostEffect
  • ④ watchSyncEffect

この記事では、上記4つのwatchについて解説していきます。

watchとwatchEffect系の2つに分類できる

4つのwatchを挙げましたが、大きく分けると watch と watchEffect系の2種類に分類されます。

残りのwatchPostEffect、watchSyncEffectの2つは 、watchEffectのオプションに
{ flush: 'post'}{ flush: 'sync' }
というオプションを付けた単なるエイリアス(別名)です
つまり、watchEffectだけで書き換えることが可能です。これらのオプションの意味についても追って解説します。

それではまず、watchEffectから解説します。

watchEffectの使い方

watchEffectとは

配下のコールバック関数内で参照している変数の、いずれかが変化する度に実行される関数です。
つまり、複数の値を監視しています。

・基本構文

watchEffect(() => {
  //変数A
  //変数B
  //このコールバック内に書いてある変数は全て監視の対象
})

上のコードでは変数Aと変数Bを配下に置いています。
それぞれの変数は監視されており、どちらか一方にでも変化があれば、watchEffectが実行されます。

次のコードでは、クリックイベントが発生する度に変数numが加算され、
それを監視しているwatchEffectも実行される例です。

const num = ref(0)
const onClick = () => num.value++ //クリックアクション
watchEffect(() => {
  console.log(num.value) //onClickが呼ばれるたびに呼び出される
})

watchEffectの実行タイミング

watchEffectの実行タイミング(フラッシュ)は3種類のうちから指定して制御できます。
pre、②sync、③postの3種類があります。
これをwatchEffectの第2引数のflushプロパティに指定します。

watchEffect(() => {
  //
},{
  flush: "sync" //or "post" or "pre"
})

それぞれの実行タイミングについて詳しく解説します。

① pre

デフォルト値です。コンポーネント更新前非同期で呼ばれます。
つまり、ライフサイクルの中では、beforeUpdate前に呼び出されます。

② sync

コンポーネント更新前、すなわちbeforeUpdate前に、同期的に、すぐさま呼び出します。
ただし、watchEffectを同期的に呼び出すのは効率が悪いので、あまりおすすめされません。
大体はpreよりも先に呼び出されることになります。

③ post

コンポーネント更新後に呼び出されます。 beforeUpdate〜updatedのタイミングです。

ライフサイクルフックとの実行順序の関係をまとめると、次のようになります。

sync pre → onBeforeUpdate → post → onUpdated

watchSyncEffectとwatchPostEffectの使い方

これら2つは、前節で紹介したflushのタイミングを、sync、postに設定しただけのwatchEffectの別名に過ぎません。ですので、効果は全く同じです。

watchの使い方

watchとwatchEffectの違い

watchも値を監視する関数ですが、次の点でwatchEffectとは異なります。

  • 監視する値を指定する
  • 監視している値の変更前後の値が取得できる
  • 監視している値の変更前後の値が取得できる

watchの使い方(プリミティブ・配列・オブジェクトの監視)

watch(監視対象, (次の状態, 前の状態) => {
  //
} )

監視対象には監視したいプリミティブ型や配列等を指定しますが、種類によって記述方法を変える必要があります。
次の状態、前の状態には監視している値の変更前後の値が渡されます。

以下に、監視対象のデータ型の違いによる記述の方法を説明します。

① プリミティブ型のref

値をそのまま代入すれば大丈夫です。

const num = ref(0)
watch(num, (next, prev) => {
  //
} )

② 配列のref

配列のコピーを返す関数を代入します。

const ary = ref<number[]>([])
watch(() => [...ary.value], (next, prev) => {
  //
} )

③ reactiveなオブジェクト

オブジェクトのコピーを返す関数を代入します。

const state = reactive({count : 0, age: 0})
watch(() => ({...state}), (next, prev) => {
  //
} )

watchで複数の値を監視する

配列でまとめて複数要素を渡せば、複数要素の監視ができます。
ただし注意点が1つあります。

配列で渡した要素のうちいずれか1つが変更されればwatchが発火しますが、同時に変更されても発火は1回しか起こりません。

//同時に変更
const onClick = () => {
  ++num1.value
  ++num2.value
}

//発火は1回
watch([num1, num2], ([nextNum1, nextNum2], [prevNum1, prevNum2]) => {
  //
})

この場合は、nextTickを用いて次のtickを待てば良いです。

//同時に変更
const onClick = async () => {
  ++num1.value
  await nextTick()
  ++num2.value
}

//それぞれ1回ずつ計2回発火
watch([num1, num2], ([nextNum1, nextNum2], [prevNum1, prevNum2]) => {
  //
})

ネストされたオブジェクトの監視(deep: true)

deep: trueプロパティをつければ、ネストが深いオブジェクトに対してもwatchが働きます。
リアクティブなオブジェクトをそのまま代入することも可能です。

const state = reactive({
  age: 0,
  type: {
    hoge: 0
  }
})
watch(() => state, (next, prev) => {
  //
},
{
    deep: true
})

コンポーネントcreated時に即実行(immidiate: true)

immidiate: trueプロパティをつければコンポーネント作成時にもwatchが実行されます。

実行タイミング(flush)

watchEffect同様、①pre、②sync、③postの3種類をflushプロパティで指定します。

まとめ

4種類のwatchについて解説しました。そのうちwatchSyncEffectとwatchPostEffectはwatchEffectの単なるエイリアスなので、基本的にはwatchとwatchEffectさえおさえておけば良いでしょう。これらの違いは実行タイミングのみです。watchはwatchEffectよりも多機能な分記述も長くなるので、使い分けが大事です。

参考