採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vue — watch / watchEffect

テクノロジ系 / L03 lifecycle — ライフサイクルと算出プロパティ

Vue — watch / watchEffect

computed と watch の役割分担

用途
computed値の派生 (=何を表示するか)
watch副作用 (=変化した時に何かする)

「変化を検知してログを取る」「変化したら API 呼ぶ」 → watch。

watch — 明示的に対象指定

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

const count = ref(0);

watch(count, (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`);
});
</script>

第1引数: 監視対象 (ref / computed / 関数)

第2引数: コールバック (new, old が来る)

複数監視

<script setup>
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  // どちらかが変わると発火
});
</script>

watchEffect — 自動追跡

<script setup>
import { ref, watchEffect } from "vue";

const userId = ref(1);
const user = ref<User | null>(null);

watchEffect(async () => {
  // 内部で参照した ref が自動で依存になる
  const res = await fetch(`/api/users/${userId.value}`);
  user.value = await res.json();
});
</script>

→ 依存を 明示せずに自動追跡 (React の useEffect の良いとこ取り)。

初回も実行される。

immediate オプション

<script setup>
// watch のデフォルトは「変化したら」発火 (初回呼ばれない)
watch(userId, fetchUser, { immediate: true });
</script>

watchEffect は常に immediate。

watch は明示で immediate: true

deep オプション

<script setup>
const user = ref({ name: "Taro", age: 20 });

// ❌ user.name を変えても発火しない
watch(user, (v) => console.log(v));

// ✅ deep: true で深く監視
watch(user, (v) => console.log(v), { deep: true });
</script>

object/配列の中身の変化を見たい時に。

cleanup

<script setup>
watchEffect(async (onCleanup) => {
  const ctrl = new AbortController();
  onCleanup(() => ctrl.abort());

  const res = await fetch(`/api/users/${userId.value}`, { signal: ctrl.signal });
  user.value = await res.json();
});
</script>

onCleanup次の実行前 に呼ばれる関数を登録。

race condition / メモリリーク対策。

React useEffect との対比

ReactVue
副作用useEffectwatch / watchEffect
依存配列手動 [a, b]自動 (watchEffect) or 明示 (watch)
cleanupreturn () => ...onCleanup(() => ...)
初回実行常にwatch は明示 (immediate)

Vue の方が 依存追跡が楽、React は明示で安全寄り。

まとめ

  • 派生値は computed、副作用は watch
  • watch(target, cb) で明示監視
  • watchEffect(cb) で自動追跡
  • immediate / deep / onCleanup のオプション
  • React useEffect との対応関係を理解

最後は ライフサイクルフック

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

Lv1: 基本 — watch と watchEffect

問題

<script setup>
const count = ref(0);
const name = ref("");

// watch: 明示
watch(count, (newVal, oldVal) => {
  console.log(`count: ${oldVal} -> ${newVal}`);
});

// watchEffect: 自動追跡
watchEffect(() => {
  console.log("effect:", count.value, name.value);
});
</script>

count や name を変えて、両方のログを観察。

合格基準

  • watch は count 変更時のみ
  • watchEffect は count or name どちらでも + 初回も実行

Lv2: 応用 — fetch を watchEffect で

問題

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

const userId = ref(1);
const user = ref<any>(null);

watchEffect(async (onCleanup) => {
  const ctrl = new AbortController();
  onCleanup(() => ctrl.abort());

  user.value = null;
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId.value}`, { signal: ctrl.signal });
  user.value = await res.json();
});
</script>

<template>
  <input v-model.number="userId" type="number" />
  <pre>{{ user }}</pre>
</template>

userId を変えると自動再 fetch + 古い fetch はキャンセル。

合格基準

  • userId 変化で自動再取得
  • onCleanup で abort

Lv3: 発展 — deep watch

問題

<script setup>
const user = ref({ name: "Taro", profile: { age: 20 } });

// ❌ deep 無しでは profile.age を変えても発火しない
watch(user, () => console.log("watch (no deep)"));

// ✅ deep: true
watch(user, () => console.log("watch deep"), { deep: true });

setTimeout(() => {
  user.value.profile.age = 21;   // どちらが発火する?
}, 1000);
</script>

deep を使わない場合と使う場合の差を確認。

合格基準

  • deep なしでは発火しない
  • deep: true で発火
  • 性能トレードオフを理解

提出

submission.md に3問の回答を。