0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vueのcomputedとwatchの使い分け:値を作るならcomputed、処理を走らせるならwatch

0
Posted at

はじめに

この記事では、Vue の computedwatch の使い分けを整理します。

どちらもリアクティブな値に関係するため、最初は似て見えます。
ただし、役割は同じではありません。

結論だけ先に言うと、判断基準は次です。

  • 表示用の値を作るなら computed
  • 値の変化をきっかけに処理を走らせるなら watch

この軸で考えると、computedwatch の使い分けはかなり整理しやすくなります。

先に結論

computed は値として使います。

例えば、姓と名からフルネームを作るような処理です。

<script setup lang="ts">
import { computed, ref } from "vue"

const lastName = ref("山田")
const firstName = ref("太郎")

const fullName = computed(() => `${lastName.value} ${firstName.value}`)
</script>

<template>
  <p>{{ fullName }}</p>
</template>

一方で、watch は値の変化をきっかけに処理を動かすために使います。

例えば、検索文字列が変わったら API を呼ぶような処理です。

<script setup lang="ts">
import { ref, watch } from "vue"

const searchText = ref("")

watch(searchText, async (text) => {
  await fetch(`/api/search?q=${encodeURIComponent(text)}`)
})
</script>

かなり雑に言うと、次の分け方です。

  • computed は結果を画面や別の処理で使う
  • watch は変化をきっかけに何かを実行する

computed は表示用の値を作るときに使う

computed は、リアクティブな値から別の値を作るときに使います。

例えば、金額と税率から税込価格を作る場合です。

<script setup lang="ts">
import { computed, ref } from "vue"

const price = ref(1000)
const taxRate = ref(0.1)

const priceWithTax = computed(() => price.value * (1 + taxRate.value))
</script>

<template>
  <p>{{ priceWithTax }}</p>
</template>

この場合、priceWithTax は状態ではありません。
pricetaxRate から作れる派生値です。

そのため、別の ref にして手動で更新するより、computed にした方が自然です。

computed に向いているのは、次のような値です。

  • 表示用に整形した文字列
  • フィルタ済みの配列
  • 合計金額
  • 入力値から作るバリデーション結果
  • ボタンを押せるかどうかの判定

どれも「元の値から作れる値」です。

watch は値の変化で処理したいときに使う

watch は、値が変わったことをきっかけに処理を動かすときに使います。

例えば、検索条件が変わったら一覧を再取得する場合です。

<script setup lang="ts">
import { ref, watch } from "vue"

const keyword = ref("")
const items = ref<string[]>([])

watch(keyword, async (value) => {
  const response = await fetch(`/api/items?keyword=${encodeURIComponent(value)}`)
  items.value = await response.json()
})
</script>

この処理の主目的は、表示用の値を計算することではありません。
keyword の変化をきっかけに、外部へ問い合わせています。

このような処理は watch の方が向いています。

watch に向いているのは、次のような処理です。

  • API を呼ぶ
  • localStorage に保存する
  • URL クエリを更新する
  • 外部ライブラリへ値を反映する
  • 値が変わったタイミングでログを送る

共通しているのは、副作用があることです。

watch で表示用の値を作るのは避ける

watch でも、別の ref に値を代入すれば表示用の値を作れます。
ただし、これは基本的には避けた方がよいです。

例えば次のような書き方です。

<script setup lang="ts">
import { ref, watch } from "vue"

const lastName = ref("山田")
const firstName = ref("太郎")
const fullName = ref("")

watch([lastName, firstName], ([newLastName, newFirstName]) => {
  fullName.value = `${newLastName} ${newFirstName}`
})
</script>

このコードでも動きます。
ただし、fullNamelastNamefirstName から作れる値です。

この場合は computed の方が素直です。

<script setup lang="ts">
import { computed, ref } from "vue"

const lastName = ref("山田")
const firstName = ref("太郎")

const fullName = computed(() => `${lastName.value} ${firstName.value}`)
</script>

watch で派生値を作ると、更新処理が増えます。
また、初期値の設定や更新漏れも気にする必要があります。

元の値から作れるだけなら、まず computed を考える方が安全です。

computed の中で副作用を起こさない

逆に、computed の中で API 通信や保存処理をするのも避けた方がよいです。

例えば次のような書き方です。

const result = computed(async () => {
  const response = await fetch(`/api/search?q=${searchText.value}`)
  return await response.json()
})

これは computed の役割から外れています。

computed は、依存している値から別の値を作るためのものです。
外部と通信したり、どこかへ保存したりする場所ではありません。

API 通信をしたいなら、watch で変化を見て処理する方が読みやすいです。

watch(searchText, async (text) => {
  const response = await fetch(`/api/search?q=${encodeURIComponent(text)}`)
  results.value = await response.json()
})

computed は値を作る場所です。
watch は処理を走らせる場所です。

この分け方を守ると、コードの意図が読みやすくなります。

よくある判断例

実務で迷いやすい例を、判断基準で整理します。

フォームの入力値からエラーメッセージを作るなら computed です。

const emailError = computed(() => {
  if (!email.value) {
    return "メールアドレスを入力してください"
  }

  if (!email.value.includes("@")) {
    return "メールアドレスの形式が正しくありません"
  }

  return ""
})

これは email から表示用の値を作っているだけです。

一方で、入力値が変わるたびに下書きを保存するなら watch です。

watch(body, (value) => {
  localStorage.setItem("draft-body", value)
})

これはブラウザの保存領域へ書き込んでいます。
つまり副作用があります。

チェックボックスの状態から送信ボタンを押せるか判定するなら computed です。

const canSubmit = computed(() => {
  return agreed.value && name.value.length > 0
})

ログイン状態が変わったら画面遷移するなら watch です。

watch(isLoggedIn, (value) => {
  if (value) {
    router.push("/home")
  }
})

このように、値を作っているのか、処理を起こしているのかで分けると判断しやすいです。

迷ったらまず computed を検討する

迷ったときは、まず computed で書けないかを考えるとよいです。

理由は、画面に必要な値の多くは「今ある状態から作れる値」だからです。

例えば次のようなものです。

  • 表示名
  • 合計金額
  • 絞り込み結果
  • 並び替え結果
  • 入力エラー
  • ボタンの disabled 判定

これらは、元の値が変われば自然に変わるべき値です。

この種の値を watch で手動更新すると、コードが少しずつ複雑になります。

watch を使うのは、「値を作る」だけでは済まないときです。

  • 通信する
  • 保存する
  • 画面遷移する
  • 外部ライブラリを操作する
  • DOM やブラウザ API と同期する

このような処理が出てきたら、watch を検討します。

まとめ

computedwatch は、どちらも Vue のリアクティブな値に関係します。
ただし、使う目的は違います。

ポイントをまとめると次のとおりです。

  1. 表示用の値を作るなら computed
  2. 値の変化をきっかけに処理するなら watch
  3. computed は値として使う
  4. watch は副作用に使う
  5. watch で派生値を作るのは基本避ける
  6. computed の中で API 通信や保存処理をするのは避ける

迷ったら、まず「これは値を作っているのか、処理を起こしているのか」を考えると整理しやすいです。

値を作っているなら computed
処理を起こしているなら watch

この分け方だけでも、Vue のコードはかなり読みやすくなります。

Vue のリアクティブな書き方全体を React と比較したい場合は、別記事「VueのComposition APIとReact Hooksは何が似ていて何が違うのか」も関連します。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?