Vue 3 リアクティブシステム:watch と watchEffect の使い分け
James Reed
Infrastructure Engineer · Leapcell

はじめに
フロントエンド開発の活気ある分野において、Vue.js はその魅力の核心であるリアクティブシステムにより、依然として強力な存在感を示しています。開発者として、私たちは常に効率的で、保守しやすく、パフォーマンスの高いアプリケーションを目指しています。Vue 3 でこれを実現するための重要な側面は、特にデータ変更への対応という点において、リアクティビティを効果的に管理することです。Vue 3 はこの目的のために、watch と watchEffect という 2 つの強力なツールを提供しています。どちらもリアクティブな状態変化に応答して副作用を実行できますが、そのニュアンスと適切なユースケースは、時に混乱の元となることがあります。これらの区別を正確に理解することは、単なる学術的な演習ではありません。これは、よりクリーンで、より最適化され、エラーの少ない Vue アプリケーションを作成することに直接つながります。この探求は、これらの区別を明確にし、特定のリアクティブなニーズにどのウォッチャーをデプロイするかについて、情報に基づいた意思決定を行えるようにすることを目的としています。
Vue 3 のリアクティブウォッチャーの理解
watch と watchEffect の詳細に入る前に、Vue 3 のリアクティビティシステムを支える基本的な概念に簡単に触れてみましょう。
リアクティビティ: Vue におけるリアクティビティとは、データが変更されたときに、そのデータに依存するアプリケーションの部分が自動的に更新されることを意味します。これは、プロパティのアクセスと変更をトラップするプロキシベースのシステムによって実現されます。
副作用: ウォッチャーの文脈では、副作用とは、リアクティブな依存関係の変更によって直接発生する操作のことです。これは、コンソールへのログ記録、API の呼び出し、DOM の直接更新、または他のリアクティブな状態の変更など、何でもあり得ます。
それでは、2 つの主要な要素を見てみましょう。
watch
watch 関数は、特定のリアクティブなデータソースへの変更に応答するための、より明示的で設定可能な方法です。監視したいものを明示的に指定する必要があります。
原則: watch は、ソース(またはソースの配列)とコールバック関数を受け取ります。コールバック関数は、ソースの値が変更されるたびに実行されます。また、監視対象ソースの新しい値と古い値の両方にアクセスできます。
実装例:
import { ref, watch } from 'vue'; export default { setup() { const count = ref(0); const searchKeyword = ref(''); // 単一の ref を監視 watch(count, (newCount, oldCount) => { console.log(`Count changed from ${oldCount} to ${newCount}`); // 副作用を実行、例:API 呼び出し // if (newCount > 5) { // console.log('Count is getting high!'); // } }); // 複数のソース(ref の配列)を監視 watch([count, searchKeyword], ([newCount, newKeyword], [oldCount, oldKeyword]) => { console.log(`Count changed from ${oldCount} to ${newCount}`); console.log(`Keyword changed from '${oldKeyword}' to '${newKeyword}'`); // 何らかの派生データを更新または複雑なロジックをトリガー }); // オブジェクトのディープ監視(明示的に有効化) const user = ref({ name: 'Alice', age: 30 }); watch(user, (newUser, oldUser) => { console.log('User object changed:', newUser); }, { deep: true }); // 即時実行(コンポーネントセットアップ時にコールバックが即座に実行され、その後変更時に実行される) watch(count, (newCount) => { console.log(`Initial count or count changed: ${newCount}`); }, { immediate: true }); const increment = () => { count.value++; }; const updateKeyword = (event) => { searchKeyword.value = event.target.value; }; const changeUserName = () => { user.value.name = 'Bob'; // これはディープウォッチャーをトリガーします }; return { count, searchKeyword, user, increment, updateKeyword, changeUserName }; } };
watch のアプリケーションシナリオ:
- 古い値と新しい値を持つ特定のデータ変更への応答: 前の状態と現在の状態を比較する必要がある場合(例:値が増加したか減少したかを判断するため、またはログ記録のため)。
- 必要な場合にのみ高コストな操作を実行: 非常に特定のデータが変更された場合にのみトリガーされるべき複雑な計算や API 呼び出しがある場合、
watchはその依存関係を特定できます。 - 複数の独立したソースの監視: 複数のリアクティブプロパティを同時に監視し、いずれかが変更されたときに応答できます。これは、クロス依存関係のロジックに役立ちます。
- ネストされたオブジェクトのディープ監視:
{ deep: true }を明示的に設定することにより、watchはオブジェクト内のネストされたプロパティの変更を検出できます。これはwatchEffectでは自動ではありません(watchEffect内で内部プロパティに直接アクセスしない限り)。 - ウォッチャーがいつ実行されるかを制御(例:
immediateオプション): コンポーネントマウント時に副作用が一度即座に実行され、その後の変更でも実行される必要がある場合。
watchEffect
watchEffect 関数は、変更に応答するための、よりシンプルで自動的な方法です。依存関係を事前に指定する必要はありません。代わりに、最初の実行中にアクセスされたリアクティブな依存関係を自動的に追跡します。
原則: watchEffect は指定された関数を即座に実行し、リアクティビティシステムは、その実行中にアクセスされたリアクティブなプロパティを自動的に追跡します。追跡された依存関係のいずれかが変更されるたびに、関数が再実行されます。
実装例:
import { ref, watchEffect } from 'vue'; export default { setup() { const firstName = ref('John'); const lastName = ref('Doe'); const age = ref(30); // firstName と lastName に反応する WatchEffect watchEffect(() => { console.log(`Full Name: ${firstName.value} ${lastName.value}`); // この watchEffect は、firstName または lastName が変更されるたびに再実行されます。 // 'age' はコールバック内でアクセスされていないため、追跡されません。 }); // クリーンアップ関数を持つ WatchEffect let timer; watchEffect((onCleanup) => { console.log(`Current age: ${age.value}`); // 非同期操作をシミュレート timer = setTimeout(() => { console.log(`Age updated to ${age.value} after delay`); }, 1000); onCleanup(() => { // この関数は、エフェクトが再実行される前、またはコンポーネントがアンマウントされるときに実行されます console.log('Cleaning up previous age effect...'); clearTimeout(timer); }); }); const updateName = () => { firstName.value = 'Jane'; lastName.value = 'Smith'; }; const increaseAge = () => { age.value++; }; return { firstName, lastName, age, updateName, increaseAge }; } };
watchEffect のアプリケーションシナリオ:
- 自動依存関係の追跡: 依存関係を明示的にリストアップすることなく、コールバック関数内でアクセスされたリアクティブな依存関係のいずれかが変更されるたびに、副作用を実行させたい場合。これにより、コードがより簡潔になることがよくあります。
- 古い値が不要な副作用の実行: 現在の状態のみを使用してアクションを実行する場合(例:派生した ref の更新、現在のデータでの API 呼び出し、またはログ記録)。
- データ同期の簡略化: 複数の内部リアクティブソースと同期を保つ必要がある派生状態または外部エフェクトがある場合。
- 副作用が計算そのものである場合: たとえば、依存関係が変更されたときにクリーンアップする必要があるサブスクリプションまたはリソースの設定(
onCleanupコールバックを使用)。
主な違いとどちらを選択するか
| 特徴 | watch | watchEffect |
|---|---|---|
| 依存関係 | 最初の引数として明示的に宣言(例:ref、computed、ゲッター関数、ソースの配列)。 | コールバック関数内で実行される同期コードから自動的に推論される。 |
| コールバック引数 | (newValue, oldValue, onCleanup) または複数のソースの場合は ([newVals], [oldVals], onCleanup) を受け取る。 | (onCleanup) のみを受け取る。newValue または oldValue は受け取らない。 |
| 初回実行 | デフォルトでは実行されない。コンポーネントセットアップ時に実行するには { immediate: true } オプションが必要。 | 依存関係を収集するために、コンポーネントセットアップ時に常に即座に実行される。 |
| ディープ監視 | オブジェクトの場合は { deep: true } オプションが必要。 | プロパティのアクセスを追跡する。obj.prop にアクセスされた場合、obj.prop を追跡する。obj のみがアクセスされた場合、内部の変更は追跡しない(obj.value に新しいオブジェクトが割り当てられない限り)。 |
| ユースケース | 特定の依存関係に反応したい場合、古い値にアクセスしたい場合、または実行をより正確に制御したい場合。 | アクセスされたリアクティブ依存関係のいずれかが変更されるたびにエフェクトを自動的に再実行させたい場合。単純な同期ロジックに適している。 |
| シンプルさ | より明示的で構成可能。 | 単純な副作用の場合は、よりシンプルで自動的。 |
正確なユースケースは次のように集約されます。
watchを選択する場合: 特定のデータソースの前の値を知る必要がある場合、または明確に定義された特定の変更にのみ反応させたい場合。これは、きめ細かな制御を提供し、値の変更に基づく条件付きロジックに不可欠です。watchEffectを選択する場合: そのブロック内でアクセスされたリアクティブ状態のいずれかが変更されるたびにコードブロックを再実行させたいだけで、正確な前の値が関連しない場合。これは、外部状態の同期や、複数のリアクティブ依存関係の現在の状態に依存する副作用の実行に最適です。
結論
Vue 3 の watch と watchEffect は、リアクティビティを管理するための開発者の武器庫において、どちらも不可欠なツールです。watch は、正確な依存関係を定義し、過去の値にアクセスできる明示的な制御を提供しますが、watchEffect は、アクセスされたすべてのリアクティブ状態に自動的に反応する、依存関係のないエレガントなアプローチを提供します。それらの独特なメカニズムとユースケースを理解することで、より効率的で、保守しやすく、宣言的な Vue アプリケーションを作成できます。最終的に、watch は制御のために特定の追跡された変更に明示的に反応するためのものであり、watchEffect はシンプルさのためにすべてのアクセスされた依存関係に暗黙的に反応するためのものです。

