vue.js

【Vue.js】computed、watch、メソッドの違いを理解する。

マナビト
マナビト
こんにちはマナビトです。
普段はフロントエンドエンジニアとしてNuxt.js/vue.js/TypeScriptを
中心に開発業務を行なっています。

今回はcomputed、watch、メソッドの違いについて記事したいと思います。

computedとは

computed(算出プロパティ)はリアクティブなデータを含む複雑なロジックを記載して、データの変更があると、自動で再計算してくれる機能を持っています。

例えば、テンプレート内で動的に変更されるemailプロパティを記載し、「@」以前の文字を小文字に変換する処理が加えられているとします。

<div>
  <p>{{ email.split('@')[0].toLowerCase() }}</p>
</div>

Vue.jsにおいては、このようなリアクティブなデータを含む複雑なロジックを処理する場合は、基本的にcomputedを使用します。返り値はrefとなります。その為、refと同じようにpublishedBooksMessage.valueで算出された結果を参照することが可能です。

<script setup>
import { reactive, computed } from 'vue'

const mail = reactive({
  email: 'something@example.com'
})

// 算出プロパティの参照
const publishedBooksMessage = computed(() => {
  return mail.email.split('@')[0].toLowerCase()
})
</script>

<template>
  <p>{{ publishedBooksMessage }}</p>
</template>

watchとは

Vue.jsではcomputed以外にもwatchが存在します。どちらも登録したプロパティの変更を検知して何らかの処理を行うプロパティです。

watchを使用することで値を監視することができ、値が変化すると登録した処理を実行することができます。

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

監視対象にはプリミティブ型や配列などを配置します。

const x = ref(0)

watch(x, (newData) => {
     console.log(${newData}`)  // xが変化する度にリアクティブな値を出力
})

リアクティブのオブジェクトの監視

リアクティブオブジェクトのプロパティを監視する場合、そのままプロパティを渡したとしても正しく動作しません。以下の指定方法だとwatchに単に数値を渡しているだけになってしまうからです。

const obj = reactive({ count: 0 })

watch(obj.count, (count) => {
  console.log(`${count}`) // 正しく動作しない。
})

オブジェクトのプロパティを監視する場合は、getterを使用して監視する必要があります。

const obj = reactive({ count: 0 })

watch(
  () => obj.count
  (sum) => {
    console.log(`${count}`)
  }
)

複数の値を監視する場合

配列にてまとめれば、複数の値を監視することが可能です。

const x = ref(0)
const y = ref(0)

// 複数の要素の配列
watch([x, y], ([newX, newY]) => {
   console.log(`x is ${newX} and y is ${newY}`)
})

配列で渡した要素のうち、一つでも変更された場合はwatchが発火しますが、同時に変更された場合は、発火は1回のみとなります。

//同時に値を変更する
const onClick = () => {
  ++increment.value
  ++increment2.value
}

//発火は1回のみ
watch([increment, increment2], (next, prev) => {
  // 処理
})

2回発火させるためにはnextTickメソッドを使用して、2回目はDOM更新後にwatchが発火するようにさせます。

const onClick = async () => {
  ++increment.value
  await nextTick() // 追加
  ++increment2.value
}

watch([increment, increment2], (next, prev) => {
  // 処理
})

ディープ

watchは自動的にディープ・ウォッチャーが適用されます。その為、ネストが深いオブジェクトに対してもwatchが作動します。

const obj = reactive(
 {
   count: 0 ,
 age: {
       year: 1980,
       month: 10
       day: 20
     }
 }
)

watch(obj, (count) => {
  console.log(`${count}`)  ネストしたプロパティの変更を検知する。
})

先ほど述べた通り、getterを使用した場合はオブジェクトが変更されない限りは、watchが発火しませんが、deepオプションを付けることで強制的にネストが深いオブジェクトに対してもwatchを作動させるようにすることが可能です。

const obj = reactive(
 {
   count: 0 ,
 age: {
       year: 1980,
       month: 10
       day: 20
     }
 }
)

watch(() => obj.count, (count) => {
  console.log(`${count}`)  ネストしたプロパティの変更を検知する。
  },
  { deep: true }
)

computedとメソッドの違い

computedとメソッドの違いについて記載していきます。

上記コードはメソッドでも以下のように書くことが可能です。

// メソッド
function publishedBooksMessage() {
  return mail.email.split('@')[0].toLowerCase()
}

一見メソッドでも問題なく動作しそうですが、computedで記載した場合とでは明確な違いがあります。

キャッシュ機能があるかどうか

computで算出された値はリアクティブなデータとしてキャッシュされ、このリアクティブデータが更新された時だけ検知されます。その為、値が変更されない限りは画面に表示される値はそのままとなります。

computedの場合は、リロードされたとしても、キャッシュの値を参照するため、データが変わらない限りは表示に変化はありません。一方でメソッドはキャッシュ機能がないため、たとえデータが変更されていない場合でも、ページをリロードする度に新しく実行されることになります。

日付を表示するなど常に表示を更新する必要があるデータを扱う場合は、メソッドを使用するべきです。computedで記載した場合は、値の変更が検知されないので、キャッシュの値を参照することになり意図した挙動にならない為です。

Getter/Setterを使用できる

computedではgetterとsetterを記述することができます。親コンポーネントからデータを渡された場合、子コンポーネントはpropsとして受け取ります。子コンポーネント内でそのデータを変更したい場合は、Setterを使用して、親コンポーネントに対してイベントを送る必要があります。

<template>
  <input type="text" :title v-model="initalValue" />
 <button @click="clickButton">ボタン</button>
</template>

<script>
import { toRef, computed } from 'vue';

export default {
  emits: ['clickButton'],
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  setup(props, emit) {
    //  ref宣言
    const state = toRef(props, 'value');

    const initalValue = computed({
      get: () => {
        return props.value;
      },
      set: (v) => {
        if (props.value !== v) {
          emit('clickButton', v);
        }
      },
    });

    return {
      state,
    };
  },
};
</script>

computedとwatchの違い

computedではなく、watch使用するケースは以下のことが考えられます。

  • 副作用が変更されるような非同期処理を実装する場合
  • 更新前と更新後の値を取り扱う場合
  • watchで記載された関数が値を返す必要がない場合
<template>
  <div id="app">
    <p>
      Ask a yes/no question:
      <input v-model="question" />
    </p>
    <p>{{ demo }}</p>
  </div>
</template>

<script>
import { ref, watch } from 'vue';

export default {
  name: 'App',
  setup() {
    const question = ref('');
    const demo = ref('');
    watch(question, async (newQuestion, oldQuestion) => {
      if (newQuestion) {
        demo.value = 'something';
        try {
          const res = await fetch('https://yesno.wtf/api');
          demo.value = (await res.json()).answer;
        } catch (error) {
          console.errer(error);
        }
      }
    });
  },
};
</script>

今日はここまでです。

■ 参考文献