🙄

Vue3 computed/watch/watchEffect 使い分け調べてみた

2023/03/17に公開

はじめに

Vueでリアクティブなデータの変更を検知して何か処理したいとき、
computedやwatch、またはwatchEffectというCompositionAPIで用意された
便利な機能があり、上記のどれかを用いることで"変更検知して処理"が容易にできます。

ただ、ふと 「computedとwatch、どっち使うべき...?」 とか 、
「watchとWatchEffectは...?」 とか迷うことがあり、
それぞれの使い分けや適切な使用方法について備忘録として記事にまとめました。

前説明

前述の通り、今回取り上げる3つの機能はいずれも リアクティブなデータの変更監視し、それに応じて処理を行うための機能です。

【computed】

<template>
    <input type="text" v-model="price">
    <p>2倍の金額:{{priceDoubleValue}}</p>
</template>

<script setup lang="ts">

const price = ref(0);

// リアクティブデータ(price.value)を監視して、
// 変更を検知したらpriceDoubleValueとして再評価して計算結果を返す
const priceDoubleValue = computed(() => {
    return price.value * 2;
})

</script>

【watch】

<template>
    <input type="text" v-model="price">
    <p>2倍の金額:{{priceDoubleValue}}</p>
</template>

<script setup lang="ts">

const price = ref(0);

const priceDoubleValue = computed(() => {
    return price.value * 2;
})

// priceDoubleValueを監視して、変更があればcallbackを実行する
watch(priceDoubleValue, (newValue, oldValue) => {
    console.log(`${oldValue}から${newValue}へ変わりました`)
})

【watchEffect】

<template>
    <input type="text" v-model="price">
    <p>2倍の金額:{{priceDoubleValue}}</p>
    <button @click="stop">ストップ</button>
</template>

<script setup lang="ts">

const price = ref(0);
const isStop = ref(false);

const priceDoubleValue = computed(() => {
    return price.value * 2;
})

// 内容されるリアクティブなデータ(isStop, priceDoubleValue)のいずれかに
// 変更を検知した場合に処理を実行する
watchEffect(() => {
    if (isStop.value) {
        console.log("ストップです!!!")
    } else {
        console.log(`${priceDoubleValue}です。まだいけます!`)
    }
})

const stop = () => {
    isStop.value = true;
}

各概要を把握した上で考えるべきは下記。

  1. computed or watch ?
  2. watch or watchEffect ? (computedではなく、watchを採用する場合)

computed or watch

色々と調べて思った個人的見解としては下記の使い分けがよさそう。
※個人的見解です。

【computed】
変更を検知して...

  • データを集計、計算など行なった上で、計算結果を受け取る
  • フィルタリングやソートなどのデータ処理を行って、加工したデータを受け取る
  • 変更された状態に応じて別のデータや状態を受け取る

変更に応じたデータを取得する場合にcomputedが適している

Vueの公式ドキュメントにもこう記述があります。

ベストプラクティス​
getter 関数は副作用のないものでなければならない​
算出プロパティにおける getter 関数は計算のみを行い、副作用がないようにすることが重要です。例えば、非同期リクエストや、DOM を変化させないようにしましょう! 算出プロパティは他の値に基づいて計算する方法を宣言的に記述していると考えてください。その唯一の責任は、値を計算して返すことでなければなりません。このガイドの後半では、 ウォッチャー を使って、状態の変化に反応して副作用を実行する方法について説明します。

【watch】
変更を検知して...

  • 特定のデータの変更を監視し、副作用を実行する場合

正直、これに尽きるのかなと感じます。
ここでいう副作用は、データの変更があったら自動的に発生する処理のことを指します。
例えば、変更を検知したらAPIにリクエストを送信する、DOMを更新する、
別のVueコンポーネントにその変更を伝える(emitで親にイベント渡すとか)、などなど。

あとwatchで記述したreturnは無視される(=watchを通してデータを返すことができない)ことも
computedとは違う性質の一つでもあります。

変更に呼応して、データを返す以外の処理を実行したい場合にwatchが適している
※データを返すの複雑さにもよりますが...

というところが個人的に考えた2つの使い分けでした。

watch or watchEffect

そもそも仕様面で下記の違いがあります。

  • watchでは第一引数に指定したデータの変更を検知してcallbackを実行する。
    watchEffectでは第一引数に指定したcallbackに内包される全てのリアクティブデータを監視し、いずれかに変更が検知されたらcallbackを実行する
  • watchではcallbackの引数で変更前後のデータを受け取れる

というところで、使い分けるなら

【watch】

  • 変更前後の値をもとに条件分岐して処理を実行したい場合
  • 監視する対象をひとつに限定できる場合

2つ目に関してはwatchを複数記述することで監視要素を複数持つことはできますが、記述が多くなる・複雑化する懸念がつきまといそう。

【watchEffect】

  • watchにするべき理由がない場合

監視対象が複数ある場合で、かつ同じような処理を実行させたい場合などは
watchEffectの方がコードがシンプルになる恩恵がある。

監視対象がひとつに絞れる、変更前後のデータを用いたい場合にはwatch
監視対象が複数ある場合にはwatchEffect
という使い分けで良いのかなと思います。

公式ドキュメントにも比較した際の概要について記述されていますのでご覧ください。

おわりに

適切に使い分けよう!

Discussion