Vue watch 完全ガイド: 使い方から実践的な活用まで

はい、承知いたしました。Vue watchの基本的な使い方から実践的な活用までを網羅し、詳細な説明を含む約5000語の記事を記述します。記事の内容を直接ここに表示します。


Vue watch 完全ガイド: 使い方から実践的な活用まで

はじめに

Vue.js は、リアクティブなデータバインディングとコンポーネントベースのアーキテクチャを特徴とするプログレッシブ JavaScript フレームワークです。Vue アプリケーションを開発する上で、データの変化に反応して特定の処理を実行することは非常に重要です。そのための強力なツールとして提供されているのが、watch プロパティ(Options API)および watch 関数(Composition API)です。

watch は、リアクティブなデータの変更を監視し、その変更が発生したときに指定されたコールバック関数を実行するメカニズムです。これは、特定のデータの変更に応じて、非同期処理の実行、複雑な計算、DOM 操作、または他の状態の更新など、さまざまな副作用をトリガーするために使用されます。

Vue のリアクティビティシステムは非常に効率的ですが、時にはデータの変更に対する明示的な「監視」と「反応」が必要になります。例えば、ユーザーが入力フィールドに値を入力した際に、その値に基づいてサーバーからデータを取得したい場合や、複数のリアクティブな状態が特定の条件を満たしたときに、あるアクションを実行したい場合などです。このようなシナリオにおいて、watch はその真価を発揮します。

本記事では、Vue.js の watch について、その基本的な使い方から、Options API および Composition API における記述方法、豊富なオプション、複数のデータを監視する方法、さらには実践的な活用例や computed プロパティ、watchEffect との違いまで、詳細かつ網羅的に解説します。約5000語に及ぶこのガイドを通じて、watch を完全に理解し、あなたの Vue アプリケーション開発に効果的に活用できるようになることを目指します。

Vue.js におけるデータ監視の基本

Vue.js のリアクティビティシステムは、データの変更を自動的に検知し、それに応じて画面表示を更新する仕組みです。しかし、単なる画面更新だけでなく、データの変更をトリガーとして特定のロジックを実行したい場合があります。Vue には、この目的のためにいくつかのメカニズムが用意されています。

  1. 算出プロパティ (computed):

    • 一つまたは複数のリアクティブなデータソースに依存して、新しい派生的な値を生成するために使用されます。
    • 依存するデータが変更されたときに自動的に再評価され、その結果がキャッシュされます。
    • 副作用のない、値を返す同期的な計算に適しています。
    • テンプレート内で複雑な式を書く代わりに使われます。
  2. watch プロパティ / 関数 (watch):

    • 特定のリアクティブなデータソースの変更を監視し、データが変更されたときに副作用のある処理を実行するために使用されます。
    • 非同期処理、DOM 操作、API 呼び出し、ログ出力など、値を返さない、または複雑なロジックを含む処理に適しています。
    • computed とは異なり、デフォルトでは値をキャッシュしません(ただし、Options API の場合は関数形式でキャッシュのような振る舞いをさせることができますが、Composition API の watch 関数は基本的に副作用トリガーです)。

computedwatch の使い分け

これは Vue 開発において非常に重要なポイントです。どちらを使うべきか迷うこともよくあります。基本的な指針は以下の通りです。

  • computed を使う場合:

    • 既存のリアクティブなデータから新しい値を計算したいとき。
    • 計算結果をキャッシュしたいとき(計算コストが高い場合など)。
    • 依存するデータが変更されたときに同期的に新しい値を生成したいとき。
    • 例: 商品リストの合計金額、フォーム入力のバリデーション結果(エラーメッセージを表示するための真偽値や文字列)、表示状態を制御するフラグなど。
  • watch を使う場合:

    • 特定のデータ変更をトリガーとして、副作用のある処理を実行したいとき。
    • 非同期処理、DOM 操作、API 呼び出し、他の状態の複雑な更新など、値を生成する以外の目的があるとき。
    • 特定の条件が満たされたときにのみ実行したいロジックがあるとき(例: 入力が一定時間停止したら検索を実行するなど)。
    • 複数のデータソースの変更を同時に監視したいとき(Composition API の watch 関数を使用)。

例えば、「ユーザーが検索クエリを入力したとき、そのクエリを使ってAPIを呼び出し、検索結果を取得する」というシナリオでは、computed で検索クエリを監視するのではなく、watch を使って検索クエリの変更を監視し、コールバック関数の中でAPI呼び出しを行うのが適切です。なぜなら、API呼び出しは副作用のある非同期処理だからです。

逆に、「ユーザーが商品の数量を変更したとき、合計金額を更新する」というシナリオでは、合計金額は数量と単価から計算される値であり、副作用は伴わないため、computed を使うのが適切です。

この違いを理解することは、Vue アプリケーションのパフォーマンスとコードの保守性を向上させる上で非常に重要です。watch は強力ですが、乱用するとコードが追いにくくなったり、意図しない副作用が発生したりする可能性があるため、適切に使い分けることが求められます。

次に、具体的な watch の使い方について、Options API と Composition API の両方で見ていきましょう。

watch の基本 (Options API)

Vue 2 以前から存在する Options API では、コンポーネントのオプションオブジェクト内に watch プロパティを定義して監視を設定します。

基本的な構文

watch プロパティはオブジェクトです。このオブジェクトのキーに監視したいデータプロパティの名前(文字列)を指定し、値にそのデータが変更されたときに実行されるコールバック関数を指定します。

javascript
export default {
data() {
return {
message: 'Hello Vue!',
count: 0
};
},
watch: {
// message プロパティの変更を監視
message(newValue, oldValue) {
console.log('message が変更されました:', oldValue, '->', newValue);
// ここに message 変更時の処理を記述
},
// count プロパティの変更を監視
count(newValue, oldValue) {
console.log('count が変更されました:', oldValue, '->', newValue);
// ここに count 変更時の処理を記述
}
}
};

監視対象の種類

Options API の watch では、以下の種類のデータを監視できます。

  1. プリミティブ型 (String, Number, Booleanなど):

    • 値そのものの変更を検知します。
    • 上記の messagecount の例のように、プロパティ名をキーに指定します。
  2. オブジェクトまたは配列:

    • デフォルトでは、オブジェクトや配列そのものへの再代入(参照の変更)のみを検知します。
    • オブジェクトのプロパティ値や配列の要素の変更といった、内部の変更を検知するには、後述する deep オプションが必要です。
    • また、ネストされたプロパティを監視したい場合は、関数または文字列パスを指定します。

コールバック関数

コールバック関数は、監視対象のデータが変更されるたびに呼び出されます。この関数は通常、以下の引数を受け取ります。

  • newValue: 変更後の新しい値。
  • oldValue: 変更前の古い値。

これらの引数を使って、変更前後の値を比較したり、新しい値に基づいて処理を行ったりできます。

ネストされたプロパティの監視 (Options API)

オブジェクトや配列の特定のネストされたプロパティの変更を監視したい場合、watch キーの値に関数を指定するか、文字列パスを指定します。

  • 関数を指定する方法:
    関数は監視したい値を返し、Vue はその関数の戻り値の変更を監視します。

    javascript
    export default {
    data() {
    return {
    user: {
    name: 'Alice',
    address: {
    city: 'Tokyo'
    }
    }
    };
    },
    watch: {
    // user.name の変更を監視
    'user.name'(newValue, oldValue) {
    console.log('ユーザー名が変更されました:', oldValue, '->', newValue);
    },
    // user.address.city の変更を監視
    // 関数を指定してネストされたプロパティを監視するのが推奨される
    // より複雑なパスや計算が必要な場合にも対応できる
    userAddressCity() {
    // この関数は監視対象の値 (user.address.city) を返す
    return this.user.address.city;
    },
    userAddressCity(newValue, oldValue) {
    console.log('ユーザーの都市が変更されました:', oldValue, '->', newValue);
    }
    }
    };

    補足: Options API では、上記の userAddressCity のように、メソッド名をキーにして監視対象のデータを返す関数を指定するという特殊な構文があります。

  • 文字列パスを指定する方法 (非推奨):
    ネストされたプロパティへのパスを文字列で指定することも可能ですが、公式ドキュメントでは非推奨とされています。Composition API の watch 関数では、関数形式でネストされたプロパティを監視するのが標準的な方法です。Options API でも関数形式の方が柔軟性があります。

    javascript
    export default {
    data() {
    return {
    user: {
    name: 'Alice'
    }
    };
    },
    watch: {
    // 'user.name' を文字列パスで指定 (非推奨)
    'user.name'(newValue, oldValue) {
    console.log('ユーザー名が変更されました:', oldValue, '->', newValue);
    }
    }
    };

    ネストが深くなると可読性が低下するため、関数形式が推奨されます。

Options API の watch はコンポーネントのインスタンスプロパティ(this.message, this.user など)を監視するのに適しています。次に、Vue 3 から導入された Composition API における watch の使い方を見ていきましょう。

Composition API での watch

Vue 3 から導入された Composition API では、watch はコンポーネントオプションではなく、setup() 関数内でインポートして使用する関数として提供されます。これにより、関連するロジックをまとめて配置しやすくなり、コンポーネントの可読性と再利用性が向上します。

watch 関数の基本構文

watch 関数は vue からインポートします。

“`javascript
import { ref, reactive, watch } from ‘vue’;

export default {
setup() {
const message = ref(‘Hello Vue!’);
const count = ref(0);
const user = reactive({
name: ‘Alice’,
address: {
city: ‘Tokyo’
}
});

// message の変更を監視
watch(message, (newValue, oldValue) => {
  console.log('message が変更されました:', oldValue, '->', newValue);
  // message 変更時の処理
});

// count の変更を監視
watch(count, (newValue, oldValue) => {
  console.log('count が変更されました:', oldValue, '->', newValue);
  // count 変更時の処理
});

// reactive オブジェクトの変更を監視
// reactive オブジェクト全体を監視する場合、newValue と oldValue は同じオブジェクトへの参照になる点に注意
// 詳細な変更を追跡したい場合は、後述の deep オプションが必要になることが多い
watch(user, (newValue, oldValue) => {
   console.log('user オブジェクトが変更されました');
   // 変更後の user オブジェクト全体: newValue
   // 変更前の user オブジェクト全体: oldValue
   // reactive なので、newValue と oldValue は同じ参照を指すことが多い
   // 具体的なプロパティの変更を検知するには deep オプションや関数監視が必要
   console.log('New:', newValue);
   console.log('Old:', oldValue);
});

return {
  message,
  count,
  user
};

}
};
“`

watch 関数のシグネチャは以下のようになります。

javascript
watch(source, callback, options?)

  • source: 監視対象です。以下のいずれかを指定できます。
    • リアクティブなデータソース(refreactivecomputed)。
    • 値を返すゲッター関数。
    • 上記を複数含む配列。
  • callback: 監視対象のデータが変更されたときに実行されるコールバック関数です。通常、(newValue, oldValue) を引数に取ります。
  • options (省略可能): オプションオブジェクトです (immediate, deep, flush など)。

監視対象の種類 (Composition API)

Composition API の watch 関数は、より柔軟にさまざまな種類のデータを監視できます。

  1. ref:

    • ref を直接 source として渡すと、その .value の変更が監視されます。

    javascript
    const count = ref(0);
    watch(count, (newValue, oldValue) => {
    console.log('count.value が変更されました:', oldValue, '->', newValue);
    });

  2. reactive:

    • reactive オブジェクトを直接 source として渡すと、そのオブジェクト内のどのプロパティがリアクティブに追跡されるかによって変更が検知されます。ただし、デフォルトではオブジェクト内のネストされたプロパティの変更は検知されません。オブジェクト全体への再代入も通常は行わないため、reactive オブジェクトを直接監視する際は deep: true オプションが必要になることが多いです。

    javascript
    const state = reactive({ count: 0, name: 'Vue' });
    watch(state, (newValue, oldValue) => {
    // state.count または state.name が変更されると実行される
    // newValue と oldValue は同じ reactive オブジェクトへの参照となる点に注意
    // deep: true がないと、ネストされたプロパティの変更は検知されない
    console.log('state が変更されました:', newValue, oldValue); // ほとんどの場合、newValue === oldValue
    });

    reactive オブジェクト全体を監視する場合、newValueoldValue同じプロキシオブジェクトへの参照になります。変更前の値が必要な場合は、コールバック内で古い値を保持するなどの工夫が必要です。しかし、通常は特定のプロパティや関数ゲッターを監視することが多いです。

  3. ゲッター関数:

    • 監視したい値を返す関数を source として渡すことができます。これにより、ネストされたプロパティや、複数のリアクティブなデータを組み合わせた値を監視できます。この方法は Options API の関数形式の監視に相当します。

    “`javascript
    const user = reactive({
    name: ‘Alice’,
    address: {
    city: ‘Tokyo’,
    zip: ‘100-0001’
    }
    });

    // user.address.city の変更を監視
    watch(
    () => user.address.city, // 監視対象の値を返すゲッター関数
    (newValue, oldValue) => {
    console.log(‘都市が変更されました:’, oldValue, ‘->’, newValue);
    }
    );

    // user.address オブジェクト全体の変更を監視 (内部のプロパティ変更は deep オプションが必要)
    watch(
    () => user.address, // user.address オブジェクトを返すゲッター関数
    (newValue, oldValue) => {
    console.log(‘address オブジェクトが変更されました’);
    },
    { deep: true } // オプションとして deep: true を指定
    );
    “`
    ゲッター関数を使うことで、リアクティブなデータソースから派生した任意の値を監視できます。これは非常に強力で柔軟な機能です。

  4. 配列:

    • 複数の監視対象を指定したい場合は、これらを配列として source に渡すことができます。これについては後述の「複数のソースを監視する」セクションで詳しく説明します。

Composition API と Options API の watch の比較

特徴 Options API watch Composition API watch 函数
定義場所 コンポーネントオプション watch setup() 関数内
監視対象の指定 プロパティ名 (文字列)、ネストパス (文字列/関数) ref, reactive, computed, ゲッター関数, 配列
ネスト監視 deep: true オプションが必要 deep: true オプションが必要
複数ソース監視 各ソースごとにエントリを定義 配列としてまとめて指定可能
this の利用 コンポーネントインスタンス (this) にアクセス可能 setup() 内では this は通常使用しない (ref, reactive などを使う)
セットアップタイミング インスタンス作成時 setup() 実行時
停止方法 ライフサイクル (beforeDestroy) と連携 watch 関数の戻り値の関数を呼び出す
用途 主に単一プロパティの副作用 より柔軟、関連ロジックをまとめる、複数ソース監視

Composition API の watch 関数は、特に複雑なコンポーネントや再利用可能なロジック(Composable)を作成する際に、関連するリアクティビティロジックをより構造的に整理できるという利点があります。

watch のオプション

watch は、その振る舞いをカスタマイズするためのいくつかのオプションを提供します。これらのオプションは、Options API では watch プロパティの値としてオブジェクト形式で、Composition API では watch 関数の第三引数としてオブジェクト形式で指定します。

主なオプションは以下の通りです。

  1. immediate: コールバックを監視開始時に即座に一度実行するかどうか。
  2. deep: オブジェクトや配列の内部の変更も検知するかどうか。
  3. flush: コールバック関数の実行タイミングを指定します(Vue の更新サイクルのどこで実行されるか)。'pre', 'post', 'sync' のいずれか。
  4. onTrack, onTrigger: デバッグ用のフック(高度な使い方)。

これらのオプションについて詳しく見ていきましょう。

immediate: true

デフォルトでは、watch のコールバック関数は監視対象のデータが最初に変更されたときに呼び出されます。しかし、immediate: true オプションを使用すると、コンポーネントがセットアップ(または作成)された直後に、監視対象の初期値でコールバック関数が一度実行されます。

これは、初期状態に基づいて何らかの処理を実行したいが、その後のデータ変更にも反応させたい場合に便利です。

Options API での immediate

javascript
export default {
data() {
return {
searchText: 'initial query'
};
},
watch: {
searchText: {
handler(newValue, oldValue) {
console.log('検索クエリ:', newValue);
// 検索API呼び出しなど
this.fetchSearchResults(newValue);
},
immediate: true // コンポーネント作成時に即座に実行
}
},
methods: {
fetchSearchResults(query) {
console.log(`Searching for: ${query}`);
// 非同期処理をシミュレート
// 実際にはここで fetch や axios などを使う
return new Promise(resolve => {
setTimeout(() => {
console.log(`Results for "${query}" loaded.`);
resolve([]); // dummy data
}, 500);
});
}
},
created() {
console.log('Component created');
// immediate: true を使用しない場合、
// ここで初期検索を行う必要があるかもしれない
}
};

Options API でオプションを使用する場合、監視対象のプロパティ名をキーとし、その値としてオブジェクトを指定します。オブジェクトには handler プロパティでコールバック関数を定義し、他のオプション(immediate, deep など)を記述します。

Composition API での immediate

“`javascript
import { ref, watch } from ‘vue’;

export default {
setup() {
const searchText = ref(‘initial query’);

watch(
  searchText,
  (newValue, oldValue) => {
    console.log('検索クエリ:', newValue);
    // 検索API呼び出しなど
    fetchSearchResults(newValue);
  },
  { immediate: true } // 第3引数のオプションオブジェクトで指定
);

const fetchSearchResults = (query) => {
  console.log(`Searching for: ${query}`);
  // 非同期処理をシミュレート
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`Results for "${query}" loaded.`);
      resolve([]); // dummy data
    }, 500);
  });
};

console.log('setup executed');

return {
  searchText
};

}
};
``
Composition API では、
watch関数の第三引数として{ immediate: true }` を渡します。

immediate: true を使うことで、コンポーネントの created (Options API) や setup (Composition API) の中で初期データ取得などのロジックを記述する必要がなくなり、監視ロジックと初期化ロジックを watch の定義場所に集約できる場合があります。ただし、コールバック関数が初回実行されるタイミングは、コンポーネントがマウントされる前(created/setup の実行中)であることに注意が必要です。もし DOM にアクセスする必要がある処理を初回実行したい場合は、後述の flush: 'post' オプションと組み合わせるか、mounted ライフサイクルフックを使用することを検討してください。

deep: true

デフォルトでは、watch は監視対象がオブジェクトや配列の場合、その参照そのものの変更のみを検知します。つまり、新しいオブジェクトや配列を再代入した場合にのみコールバックが実行されます。オブジェクトのプロパティの値の変更や、配列の要素の追加/削除/変更といった内部の変更は検知しません。

これらの内部の変更も監視したい場合は、deep: true オプションを使用します。deep: true を指定すると、Vue は監視対象のオブジェクトや配列の内部を再帰的に辿り、すべてのプロパティや要素の変更を監視します。

Options API での deep

javascript
export default {
data() {
return {
user: {
name: 'Alice',
hobbies: ['reading', 'hiking']
}
};
},
watch: {
user: {
handler(newValue, oldValue) {
console.log('user オブジェクトが深く変更されました');
// user.name = 'Bob' や user.hobbies.push('coding') などで実行される
console.log('New:', newValue);
// Options API では deep: true とハンドラ関数を使う場合、
// oldValue は変更前のオブジェクトのディープコピーに近いものが提供されることがある
// ただし、常に保証されるわけではないため注意が必要
console.log('Old:', oldValue);
},
deep: true // 内部の変更も監視
}
}
};

Options API の watchdeep: truehandler を使う場合、Vue は oldValue として変更前の状態のディープコピーを提供することがあります。しかし、これはパフォーマンスコストが高いため、必要最小限の使用に留めるべきです。

Composition API での deep

“`javascript
import { reactive, watch } from ‘vue’;

export default {
setup() {
const user = reactive({
name: ‘Alice’,
hobbies: [‘reading’, ‘hiking’],
address: {
city: ‘Tokyo’
}
});

// user オブジェクト全体の内部変更を深く監視
watch(
  user,
  (newValue, oldValue) => {
    console.log('user オブジェクトが深く変更されました');
    // user.name = 'Bob'; user.hobbies.push('coding'); user.address.city = 'Osaka';
    // などで実行される
    console.log('New:', newValue); // reactive オブジェクトへの参照
    console.log('Old:', oldValue); // reactive オブジェクトへの参照 (newValueと同じ)
  },
  { deep: true } // オプションとして deep: true を指定
);

// 特定のネストされたオブジェクトのみを深く監視したい場合は、ゲッター関数と deep: true を組み合わせる
watch(
  () => user.address, // user.address オブジェクトを返すゲッター関数
  (newValue, oldValue) => {
    console.log('address オブジェクトが深く変更されました');
    // user.address.city = 'Osaka'; で実行される
    console.log('New:', newValue); // reactive オブジェクトへの参照
    console.log('Old:', oldValue); // reactive オブジェクトへの参照 (newValueと同じ)
  },
  { deep: true }
);


return {
  user
};

}
};
``
Composition API の
watchdeep: trueを使う場合、newValueoldValue` は同じ reactive オブジェクト(プロキシ)への参照になります。これは、reactive オブジェクトは常に同じオブジェクトインスタンスを参照し続けるからです。もし変更前の値が必要な場合は、コールバックの最初に現在の値をクローンして保存するなどの工夫が必要です。ただし、多くの場合は変更後の新しい値だけを使って処理すれば十分です。

deep: true の注意点

deep: true は非常に便利ですが、パフォーマンスコストが高いという欠点があります。deep: true を指定すると、監視対象のオブジェクトや配列が大きかったり、ネストが深かったりする場合、変更があるたびに Vue はその構造全体を再帰的に走査して変更を検出する必要があります。これは、特に頻繁に変更される大きなデータ構造に対して使用すると、アプリケーションの反応性を低下させる可能性があります。

したがって、deep: true は必要な場合にのみ使用し、可能であれば以下の代替手段を検討してください。

  • 特定のネストされたプロパティをゲッター関数で監視する: オブジェクト全体ではなく、関心のある特定のネストされたプロパティのみをゲッター関数で監視すれば、不必要な深層監視を避けられます。
  • オブジェクト構造をフラットにする: 監視したいデータがネストされている場合、可能であればデータ構造をフラット化することを検討します。
  • 変更をより高レベルで処理する: 内部の変更ではなく、オブジェクトや配列が再代入されたときや、要素が追加/削除されたときなど、より高レベルのイベントで処理できないか検討します。

flush: コールバックの実行タイミング

flush オプションは、watch コールバック関数がいつ実行されるかを制御します。Vue のリアクティビティシステムは、データの変更を検知してから実際に DOM が更新されるまでにいくつかのステップを踏みます。flush オプションを使うことで、この更新サイクルのどの段階でコールバックを実行するかを指定できます。

指定できる値は以下の通りです。

  • 'pre' (デフォルト):

    • コンポーネントの更新に実行されます。
    • ほとんどの watch コールバックのデフォルトのタイミングです。これにより、DOM 更新前にリアクティブな状態の変更を処理できます。
  • 'post':

    • コンポーネントの更新に実行されます。
    • DOM が更新された後に実行されるため、コールバック内で DOM にアクセスしたい場合に便利です(例: 要素のサイズを取得する、スクロール位置を調整するなど)。
    • コンポーネントの updated ライフサイクルフックに似ています。
  • 'sync' (非推奨):

    • 監視対象のデータが変更された直後に、DOM 更新を待たずに同期的に実行されます。
    • パフォーマンス問題を引き起こしやすく、デバッグが難しくなるため、特殊な事情がない限り避けるべきです。ほとんどのケースでは 'pre' または 'post' を使用します。

Options API での flush

Options API では、watch オプションとして flush を指定します。ただし、Options API の通常の watch はデフォルトで flush: 'pre' と同等の振る舞いをします。flush: 'post' のような DOM 更新後の処理は、通常 updated ライフサイクルフックで行います。Options API の watch オプションでは flush を直接指定することは一般的ではありません(これは Composition API に強く関連する概念です)。

Composition API での flush

Composition API の watch 関数では、第三引数のオプションオブジェクトで flush を指定します。

“`javascript
import { ref, watch, nextTick } from ‘vue’;

export default {
setup() {
const count = ref(0);
const myElement = ref(null); // テンプレート参照

// デフォルト (flush: 'pre')
watch(count, (newValue, oldValue) => {
  console.log(`[pre] count changed from ${oldValue} to ${newValue}. DOM might not be updated yet.`);
  // DOM アクセスはまだ推奨されない
});

// flush: 'post'
watch(count, (newValue, oldValue) => {
  console.log(`[post] count changed from ${oldValue} to ${newValue}. DOM is updated.`);
  // DOM アクセスが可能
  if (myElement.value) {
    console.log('Element height after update:', myElement.value.offsetHeight);
  }
}, { flush: 'post' });

// テンプレート参照を返す
return {
  count,
  myElement
};

},
mounted() {
// flush: ‘post’ ウォッチャーは mounted より後に実行される
console.log(‘Component mounted’);
}
};
**テンプレート**html

``flush: ‘post’を指定することで、DOM が更新された後にコールバックが実行されることが保証されます。これは、コールバック内でテンプレート参照(ref=”myElement”` などで取得した DOM 要素)にアクセスして、そのサイズや位置を取得したり、JavaScript で DOM を直接操作したりする必要がある場合に非常に役立ちます。

nextTick() 関数も DOM 更新後の処理に使われますが、これは特定の DOM 更新が必要な変更を行った直後に一度だけ処理を実行したい場合に便利です。flush: 'post' ウォッチャーは、監視対象の値が変更されるたびに(かつDOMが更新されるたびに)実行される点が異なります。

onTrack, onTrigger (デバッグ用)

これらのオプションは、主にデバッグのために使用されます。監視対象の値が Vue のリアクティビティシステムによって「追跡 (track)」されたとき(つまり、コールバック関数内でアクセスされて依存関係が記録されたとき)、または「トリガー (trigger)」されたとき(つまり、依存するデータが変更されてコールバックが実行される準備ができたとき)に、指定したフック関数が呼び出されます。

これらのフックは、意図しない依存関係や、なぜウォッチャーが予期せず実行されるのか(または実行されないのか)を理解するのに役立ちます。

“`javascript
import { ref, watch } from ‘vue’;

export default {
setup() {
const count = ref(0);

watch(
  count,
  (newValue, oldValue) => {
    console.log('count changed:', newValue);
  },
  {
    onTrack(e) {
      // 何がこの watch を追跡しているか(依存関係として登録したか)がわかる
      console.log('Tracking:', e);
      /* e は以下の構造を持つ
       {
         effect: // Reactive effect (この watch のコールバック関数)
         target: // 依存関係のオブジェクト (ref の場合は .value の親)
         type: // 'get'
         key: // 依存関係のキー ('value' for ref)
       }
      */
    },
    onTrigger(e) {
      // 何がこの watch をトリガーしたか(依存関係が変更されたか)がわかる
      console.log('Triggered:', e);
       /* e は以下の構造を持つ
       {
         effect: // Reactive effect (この watch のコールバック関数)
         target: // 変更されたオブジェクト (ref の場合は .value の親)
         type: // 'set', 'add', 'delete', etc.
         key: // 変更されたキー ('value' for ref)
         newValue: // 新しい値
       }
      */
    }
  }
);

// ... return values
return { count };

}
};
“`
これらのフックは開発ビルドでのみ機能し、プロダクションビルドでは無視されます。一般的なアプリケーション開発で日常的に使用するものではありませんが、複雑なリアクティビティの問題をデバッグする際には非常に強力なツールとなり得ます。

複数のソースを監視する

単一のリアクティブなデータだけでなく、複数のデータソースのいずれかが変更されたときに、一つのコールバック関数を実行したい場合があります。watch はこのような要件にも対応しています。

Options API での複数ソース監視

Options API では、それぞれの監視対象に対して個別の watch エントリを定義するのが一般的です。しかし、一つのコールバック関数で複数のプロパティの変更を処理したい場合は、ゲッター関数を使用して、監視したい複数のプロパティを含むオブジェクトや配列を返すようにします。この場合、deep: true オプションが必要になることが多いです。

“`javascript
export default {
data() {
return {
firstName: ‘John’,
lastName: ‘Doe’,
age: 30
};
},
watch: {
// firstName または lastName のいずれかが変更されたときに実行
// 監視対象として、それらを含むオブジェクトを返すゲッター関数を指定
fullName: {
handler(newValue, oldValue) {
console.log(‘氏名が変更されました:’, oldValue.firstName, oldValue.lastName, ‘->’, newValue.firstName, newValue.lastName);
// または newValue (ゲッター関数の戻り値) を直接使う
console.log(‘新しい氏名オブジェクト:’, newValue);
// oldValue は変更前のオブジェクトのディープコピーに近いものになる場合がある
},
// firstName または lastName のプロパティ値の変更を検知するため deep: true が必要
deep: true,
// 監視対象の値を返す関数
get() {
return {
firstName: this.firstName,
lastName: this.lastName
};
}
},

// 別の例: firstName, lastName, age のいずれかが変更されたときに実行
userInfo: {
  handler(newValue, oldValue) {
     console.log('ユーザー情報が変更されました', newValue);
  },
  deep: true,
  get() {
    return {
      firstName: this.firstName,
      lastName: this.lastName,
      age: this.age
    };
  }
}

}
};
``
Options API でゲッター関数 (
get()メソッド) とdeep: trueを組み合わせて複数ソースを監視する方法は少し冗長に見えるかもしれません。これは Composition API のwatch` 関数が、このニーズに直接対応しているためです。

Composition API での複数ソース監視

Composition API の watch 関数は、source として監視対象の配列を受け取ることができます。これは複数ソース監視の最もクリーンな方法です。

“`javascript
import { ref, reactive, watch } from ‘vue’;

export default {
setup() {
const firstName = ref(‘John’);
const lastName = ref(‘Doe’);
const age = ref(30);
const user = reactive({ city: ‘Tokyo’ });

// firstName または lastName のいずれかが変更されたときに実行
watch(
  [firstName, lastName], // 監視対象を配列で指定
  ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
    console.log(`氏名が変更されました: ${oldFirstName} ${oldLastName} -> ${newFirstName} ${newLastName}`);
    // ここで他の処理を実行
  }
);

// firstName, lastName, user.city のいずれかが変更されたときに実行
watch(
  [firstName, lastName, () => user.city], // reactive のプロパティはゲッター関数で指定
  ([newFirstName, newLastName, newUserCity], [oldFirstName, oldLastName, oldUserCity]) => {
     console.log('ユーザー情報の一部が変更されました');
     console.log('New:', newFirstName, newLastName, newUserCity);
     console.log('Old:', oldFirstName, oldLastName, oldUserCity);
  }
);

// firstName の変更と、user オブジェクト全体の内部変更(deep: true)の両方を監視
watch(
  [firstName, user], // user オブジェクト全体を監視対象に含める
  ([newFirstName, newUser], [oldFirstName, oldUser]) => {
     console.log('firstName または user オブジェクトの内部が変更されました');
     console.log('New firstName:', newFirstName, 'New User:', newUser);
     // reactive オブジェクトを配列監視した場合も、newValue と oldValue は参照が同じになる
     console.log('Old firstName:', oldFirstName, 'Old User:', oldUser);
  },
  { deep: true } // 配列内のいずれかのソースに deep 監視が必要な場合、オプションを指定
                  // ただし、deep: true が配列全体に適用されるわけではない
                  // 実際には、配列内のオブジェクトや配列ソースに対して deep 監視が行われる
);


return {
  firstName,
  lastName,
  age,
  user
};

}
};
``
Composition API で複数ソースを配列として監視する場合、コールバック関数の引数
newValueoldValue` は、それぞれ監視対象のソースに対応する値の配列になります。これにより、どのソースが変更されたかにかかわらず、単一のコールバックでまとめて変更を処理できます。

この配列形式の監視は、複数の入力フィールドの値を組み合わせてバリデーションを行う、複数のフィルター条件が変更されたときにリストを再取得する、といったシナリオで非常に有用です。

watch の停止

watch ウォッチャーは、それが定義されたコンポーネントインスタンスが破棄されるときに、自動的にクリーンアップされます。これはメモリリークを防ぐために重要です。

しかし、コンポーネントのライフサイクルとは独立して、特定の条件が満たされたときにウォッチャーを停止したい場合があります。例えば、初回データ取得が完了したら監視を停止して、その後の無関係な変更による不要な処理を防ぎたい、といったケースです。

Options API での watch の停止

Options API の watch プロパティで定義されたウォッチャーを明示的に停止するには、コンポーネントインスタンスの $watch メソッドを使用します。$watch メソッドは、監視を開始し、ウォッチャーを停止するための関数を返します。

“`javascript
export default {
data() {
return {
someValue: 0
};
},
created() {
// $watch メソッドでウォッチャーを定義し、停止関数を取得
const unwatch = this.$watch(‘someValue’, (newValue, oldValue) => {
console.log(‘someValue changed:’, newValue);
// 例: someValue が 10 になったら監視を停止
if (newValue === 10) {
unwatch(); // ウォッチャーを停止
console.log(‘Watcher stopped.’);
}
});

// コンポーネントのライフサイクル内で明示的に停止することも可能
// this.$once('hook:beforeDestroy', unwatch); // コンポーネント破棄時に停止

},
methods: {
increment() {
this.someValue++;
}
}
};
``
Options API の
watchプロパティで定義されたウォッチャーは、コンポーネントのbeforeDestroy(Vue 2) またはunmounted(Vue 3) ライフサイクルフックで自動的にクリーンアップされます。明示的な停止が必要なのは、上記のように特定の条件で早期に停止したい場合や、コンポーネントインスタンスとは独立した場所(例: グローバルなイベントバスの購読など、$watch` 以外の方法で設定した監視)をクリーンアップしたい場合です。

Composition API での watch の停止

Composition API の watch 関数は、ウォッチャーを停止するための関数を返します。これを呼び出すことで、いつでもウォッチャーを停止できます。

“`javascript
import { ref, watch } from ‘vue’;
import { onUnmounted } from ‘vue’; // ウォッチャーを自動停止させる場合にインポート

export default {
setup() {
const count = ref(0);

// watch 関数はウォッチャー停止用の関数を返す
const stopWatcher = watch(count, (newValue, oldValue) => {
  console.log('count changed:', newValue);
  // 例: count が 5 になったら監視を停止
  if (newValue === 5) {
    stopWatcher(); // ウォッチャーを停止
    console.log('Watcher stopped.');
  }
});

// setup 関数内で定義された watch は、
// setup が実行されたコンポーネントインスタンスがアンマウントされるときに自動的に停止される
// しかし、明示的に停止関数を保持しておけば、いつでも手動で停止できる
// 例えば、条件付きレンダリングで要素が非表示になったときなどに手動停止することも可能

// コンポーネントアンマウント時に明示的に停止したい場合(通常は自動)
// onUnmounted(() => {
//   stopWatcher();
//   console.log('Watcher stopped automatically on unmount.');
// });

return {
  count
};

}
};
``
Composition API の
setup()関数内で同期的に定義されたwatchウォッチャーは、そのsetupが属するコンポーネントインスタンスがアンマウントされるときに自動的に停止されます。これはonUnmounted` ライフサイクルフックに自動的に登録されるのと同等です。したがって、ほとんどの場合、明示的に停止関数を呼び出す必要はありません。

ただし、以下のようなケースでは明示的な停止が必要です。

  • 特定の条件が満たされたときに早期にウォッチャーを停止したい場合(上記の例)。
  • 非同期処理(setTimeout, fetch のコールバックなど)の中で watch を定義した場合、そのウォッチャーは特定のコンポーネントインスタンスに関連付けられない可能性があるため、手動で停止する必要があります。
  • watch ウォッチャーがコンポーネントの外部(例えば、Vue Router のナビゲーションガードや、Vuex ストアの購読)で定義されている場合、手動でクリーンアップが必要です。

メモリリーク防止の重要性

不要になったウォッチャーを適切に停止することは、メモリリークを防ぐ上で非常に重要です。停止しないウォッチャーは、コンポーネントが DOM から削除された後も存在し続け、監視対象のデータへの参照を保持するため、ガベージコレクションの対象とならず、メモリを消費し続ける可能性があります。特に、頻繁に生成/破棄されるコンポーネントや、 SPA でページ遷移が多いアプリケーションでは、注意が必要です。Vue の自動クリーンアップ機能は便利ですが、手動で設定した監視や、ライフサイクル外で設定した監視については、開発者が責任を持って停止する必要があります。

watch の実践的な活用例

watch は、その柔軟性から幅広いシナリオで活用できます。ここでは、いくつかの実践的な活用例を紹介します。

1. 非同期処理のトリガー (API呼び出し)

最も一般的な watch の活用例の一つは、特定のデータの変更をトリガーとして非同期処理(特にサーバーへのAPI呼び出し)を実行することです。

例えば、検索入力フィールドの値が変更されたときに、検索結果を非同期で取得するシナリオです。

Composition API (<script setup>) での例

“`html

``
この例では、
searchTextの変更をwatchし、変更があるたびにfetchSearchResults関数(非同期関数)を実行しています。コールバック関数にasyncを付けることで、非同期処理をawait` で待つことができます。

補足: Debounce と AbortController

ユーザーが検索クエリを素早く入力する場合、一文字入力するたびに watch コールバックが実行され、API呼び出しが頻繁に行われる可能性があります。これはサーバーに負荷をかけたり、不要なネットワークリクエストを発生させたりするため、多くの場合望ましくありません。

これを避けるためには、debounce という手法がよく用いられます。これは、イベント(ここでは入力値の変更)が一定時間発生しなかった場合にのみ、指定された処理を実行するものです。lodashdebounce 関数などを利用できます。

“`javascript
import { ref, watch } from ‘vue’;
import debounce from ‘lodash/debounce’; // lodash から debounce をインポート

// … (searchText, searchResults, isLoading, fetchSearchResults の定義) …

// debounce 化した fetchSearchResults 関数を作成
const debouncedFetchSearchResults = debounce(fetchSearchResults, 300); // 300ms の遅延

// searchText の変更を監視し、debounce 化した関数を実行
watch(searchText, (newQuery) => {
debouncedFetchSearchResults(newQuery);
});

// コンポーネントがアンマウントされる際に debounce をキャンセルする(メモリリーク防止)
import { onUnmounted } from ‘vue’;
onUnmounted(() => {
debouncedFetchSearchResults.cancel();
});
``
このように
debounce` を使うことで、ユーザーの入力が一時停止した後に一度だけ API が呼び出されるようになります。

さらに、ユーザーが新しい検索クエリを入力した際に、まだ完了していない前の API リクエストをキャンセルしたい場合もあります。これは、最新の検索結果のみを表示し、古い検索結果が表示されてしまう「競合状態」を防ぐために重要です。これには AbortController API を使用できます。

fetchSearchResults 関数内で AbortController を使用する例(抜粋):

“`javascript
const controller = ref(null); // 現在のリクエストの AbortController を保持

const fetchSearchResults = async (query) => {
if (controller.value) {
controller.value.abort(); // 前のリクエストをキャンセル
console.log(‘Previous request aborted.’);
}

if (!query) {
searchResults.value = [];
return;
}

controller.value = new AbortController();
const signal = controller.value.signal;

isLoading.value = true;
console.log(Fetching results for "${query}"...);

try {
const response = await fetch(https://api.example.com/search?q=${query}, { signal }); // signal を fetch オプションに渡す
if (!response.ok) throw new Error(HTTP error! status: ${response.status});
const data = await response.json();
searchResults.value = data;
} catch (error) {
// AbortError は無視する
if (error.name === ‘AbortError’) {
console.log(‘Fetch aborted.’);
} else {
console.error(‘Error fetching search results:’, error);
searchResults.value = [];
}
} finally {
isLoading.value = false;
controller.value = null; // リクエスト完了後にコントローラーをクリア
}
};
``watch` と非同期処理を組み合わせる際には、これらの高度なテクニックを検討することで、アプリケーションのパフォーマンスと安定性を大幅に向上させることができます。

2. フォーム入力のバリデーション

ユーザーがフォームに入力した値に対して、リアルタイムで複雑なバリデーションを実行し、エラーメッセージを表示したい場合があります。単純なバリデーションは computed プロパティで行えますが、非同期バリデーションや、複数の入力値に依存する複雑なバリデーションで副作用(例: エラーメッセージの表示/非表示状態の管理)が必要な場合は、watch が適しています。

“`html

``
この例では、
emailpasswordの変更をそれぞれwatchし、コールバック関数内でバリデーションロジックを実行して、対応するエラーメッセージ用のrefを更新しています。immediate: true` を使用して、コンポーネント表示時に初期値に対してもバリデーションが行われるようにしています。

3. URLパラメータの変更に応じたデータ取得

シングルページアプリケーション (SPA) では、URL のパラメータ(クエリパラメータやパスパラメータ)が変更されたときに、それに基づいて表示内容を更新したり、データを再取得したりすることがよくあります。Vue Router を使用している場合、現在のルート情報($route プロパティまたは useRoute コンポーザブル)はリアクティブなので、watch を使って監視できます。

Composition API (<script setup>) と Vue Router での例

```html

``
この例では、
useRoute().params.idを返すゲッター関数を監視対象に指定しています。URL の:idパラメータが変更されるたびにfetchPost関数が呼び出され、新しい投稿データが取得されます。immediate: true` オプションにより、コンポーネントがロードされたときの最初の ID でもデータが取得されます。

クエリパラメータ(例: /search?q=vue)を監視したい場合は、() => route.query.q を監視対象に指定します。複数のパラメータに反応したい場合は、監視対象の配列に () => route.params.id() => route.query.filter などを追加します。

4. 状態の変化に基づく UI 更新

特定のリアクティブな状態の変更に応じて、UI の見た目や表示状態を変更したい場合があります。例えば、配列の要素数に基づいて特定の要素を表示/非表示にする、エラーフラグが true になったら入力フィールドにエラークラスを付与するなどです。単純な場合は v-ifv-bind:class で十分ですが、状態の変化が複雑な条件に依存する場合や、それに伴う副作用(例: モーダルウィンドウを開く/閉じる)が必要な場合は、watch が有用です。

```html

``
この例では、
showModalの変更を監視し、それに合わせてdocument.bodyにクラスを付与/削除することで、モーダル表示時の背景スクロールを制御しています。また、countが特定の閾値を超えたらshowModaltrueにするという、別の状態に基づいたshowModalの更新も行っています。このように、watch` は状態遷移に伴う副作用を管理するのに役立ちます。

watch(count, ...) のような、ある値が変更されたときに別の値を更新するようなロジックは、場合によっては computed のセッターや、リアクティブなデータ更新関数の中で直接行うこともできますが、watch を使うことで変更の「反応」として明確に表現できます。

watch を使う上での注意点とベストプラクティス

watch は非常に強力で柔軟なツールですが、その性質上、コードが複雑になりやすく、パフォーマンス問題を引き起こす可能性もあります。効果的に、そして安全に watch を使用するために、以下の注意点とベストプラクティスを考慮しましょう。

  1. 副作用を伴う処理に使う:
    watch は、データ変更に応じて「何かを行う」ためのものです。具体的には、API呼び出し、DOM操作、外部ライブラリとの連携、ロギングなど、値を返すことだけが目的ではない副作用を伴う処理に適しています。派生的な値を計算するだけであれば、computed を使うべきです。

  2. computed との使い分けを明確にする:
    「算出 vs 監視」のセクションで述べたように、computedwatch は目的が異なります。これを混同すると、コードが非効率になったり、意図しない挙動を引き起こしたりします。新しい値を生成するなら computed、副作用をトリガーするなら watch と覚えましょう。

  3. パフォーマンスへの影響を考慮する (特に deep):
    deep: true オプションは便利ですが、大規模なオブジェクトや配列に対して使用すると、変更検出のコストが高くなり、アプリケーションの応答性が低下する可能性があります。必要な場合にのみ deep を使用し、可能であればゲッター関数で特定のネストされたプロパティのみを監視したり、データ構造をフラット化したりすることを検討してください。

  4. 複雑になりすぎないようにする:
    一つの watch コールバック内で複数の複雑な処理を行ったり、複数の watch が互いに連鎖的にトリガーされるような設計は避けるべきです。コードが追いにくくなり、予期しないループや無限更新を引き起こす可能性があります。関連するロジックは関数に切り出し、watch のコールバックはできるだけシンプルに保つように心がけましょう。

  5. 監視対象を絞る:
    必要以上に広い範囲のデータを監視しないようにしましょう。オブジェクト全体や配列全体を deep 監視する前に、本当に必要なのはその中の特定のプロパティの変更だけではないか検討してください。ゲッター関数を使うことで、監視対象を必要な値に絞り込むことができます。

  6. 適切な flush タイミングを選ぶ:
    DOM アクセスを必要とする処理を行う場合は、必ず flush: 'post' オプションを使用してください。デフォルトの 'pre' タイミングでは、DOM がまだ更新されていないため、古い DOM 構造を操作してしまう可能性があります。

  7. 不要になったウォッチャーを停止する:
    コンポーネントのライフサイクルとは無関係に定義されたウォッチャー(例: グローバルイベントリスナー、非同期処理内の watch)や、特定の条件が満たされたら役割を終えるウォッチャーは、明示的に停止することを忘れないでください。Composition API の watch が返す停止関数や、Options API の $watch が返す停止関数を活用し、メモリリークを防ぎましょう。

  8. watchEffect と比較検討する:
    次に説明する watchEffect は、依存関係を自動的に追跡するという点で watch とは異なります。どちらを使うべきか迷う場合は、以下の点を考慮してください。

    • 古い値と新しい値を比較して処理を分けたい場合は watch
    • 非同期処理などで、前の処理をキャンセルしたい場合は watchAbortController
    • 依存関係が明確で、コールバックが実行されるたびにすべての依存関係の最新値を使って処理を実行したい場合は watchEffect
    • 初期実行が常に必要な場合は watchEffect (または watch with immediate: true)。
    • 監視対象を明示的に指定したい場合は watch

これらの注意点とベストプラクティスを守ることで、watch を効果的かつ安全に活用し、堅牢で保守性の高い Vue アプリケーションを構築できます。

watchEffect との比較

Vue 3 の Composition API には、watch 関数の他に watchEffect という類似の関数があります。これらはどちらもリアクティブなデータ変更に反応して副作用を実行するために使用されますが、その動作原理と使い分けのポイントが異なります。

watchEffect とは

watchEffect 関数は、副作用関数を受け取ります。この関数の中でリアクティブなデータソース(ref, reactive, computed)がアクセスされると、Vue は自動的にそれらを依存関係として追跡します。その後、追跡された依存関係のいずれかが変更されるたびに、副作用関数が再実行されます。

watchEffect は、watchimmediate: true オプションが常に有効になっているかのように、セットアップ時に副作用関数を即座に一度実行します。この最初の実行中に依存関係が収集されます。

基本的な構文

```javascript
import { ref, watchEffect } from 'vue';

export default {
setup() {
const count = ref(0);
const name = ref('Vue');

// count または name のいずれかが変更されると実行される
const stopEffect = watchEffect(() => {
  console.log(`Effect executed. Count is ${count.value}, Name is ${name.value}`);
  // この関数内で count.value と name.value がアクセスされているため、
  // これらが自動的に依存関係として追跡される
});

// watchEffect も停止関数を返す
// onUnmounted(() => stopEffect()); // コンポーネントアンマウント時に自動停止 (通常不要)

return {
  count,
  name
};

}
};
```

watchwatchEffect の違い

特徴 watch 関数 watchEffect 関数
監視対象の指定 明示的に指定 (source 引数) 副作用関数内でアクセスされたリアクティブデータ
依存関係の収集 source 引数で指定されたもの 副作用関数の最初の実行中に自動収集
コールバックの引数 (newValue, oldValue) なし (最新の値は副作用関数内で直接アクセス)
初期実行 (immediate) オプション (immediate: true) で制御 常に即座に実行される (最初の依存関係収集も兼ねる)
古い値へのアクセス oldValue 引数で容易 コールバック内で手動で保持する必要がある
特定の条件での再実行 ソースの値が実際に変更されたとき 追跡された依存関係のいずれかが変更されたとき
クリーンアップ コールバック関数から onInvalidate を呼び出す 副作用関数からクリーンアップ関数を返す

クリーンアップ関数の違い

watchwatchEffect は、非同期処理などを行う際に、不要になった処理をキャンセルするためのクリーンアップ方法が異なります。これは、前の変更に対する非同期処理が完了する前に新しい変更が発生した場合に、競合状態を防ぐために重要です。

  • watch のクリーンアップ (onInvalidate):
    watch コールバック関数は、第三引数として onInvalidate という関数を受け取ることができます。この onInvalidate に渡された関数は、ウォッチャーが次回再実行される前、またはウォッチャーが停止される前に呼び出されます。非同期処理をキャンセルするために使用します。

    ```javascript
    watch(query, async (newQuery, oldQuery, onInvalidate) => {
    // 前の処理をキャンセルするためのクリーンアップ処理を定義
    let cancelled = false;
    onInvalidate(() => {
    cancelled = true; // キャンセルフラグを立てる
    console.log('Previous fetch invalidated.');
    });

    if (!newQuery) return;
    console.log(Fetching for ${newQuery});

    // 非同期処理 (fetch など)
    // AbortController を使う場合も onInvalidate で abort() を呼び出す
    const response = await fetch(/api/search?q=${newQuery});

    if (!cancelled) { // キャンセルされていない場合のみ結果を処理
    const data = await response.json();
    console.log('Received data:', data);
    // ... データ更新 ...
    } else {
    console.log('Data fetching was cancelled.');
    }
    });
    ```

  • watchEffect のクリーンアップ:
    watchEffect に渡す副作用関数は、クリーンアップ関数を返すことができます。この返されたクリーンアップ関数は、副作用が次回再実行される前、またはウォッチャーが停止される前に呼び出されます。

    ```javascript
    watchEffect((onInvalidate) => {
    const currentQuery = query.value; // 依存関係として追跡

    let cancelled = false;
    onInvalidate(() => {
    cancelled = true; // キャンセルフラグを立てる
    console.log('Previous effect invalidated.');
    });

    if (!currentQuery) return;
    console.log(Executing effect for ${currentQuery});

    // 非同期処理(タイマー、イベントリスナー設定など)
    // 例: タイマーを設定
    const timer = setTimeout(() => {
    if (!cancelled) {
    console.log(Delayed action for ${currentQuery});
    } else {
    console.log(Delayed action cancelled for ${currentQuery});
    }
    }, 1000);

    // クリーンアップ関数を返す
    return () => {
    // 副作用関数が次回実行される前、または watchEffect が停止される前に呼び出される
    clearTimeout(timer); // タイマーをクリア
    console.log('Effect cleanup called.');
    };
    });
    ``watchEffect` のクリーンアップは、副作用関数が返す関数として定義されるため、非同期処理のキャンセルだけでなく、イベントリスナーの解除、タイマーのクリア、購読の解除など、様々なリソースクリーンアップに利用できます。

watchwatchEffect の使い分け

  • watch を使うべき場合:

    • リアクティブなデータが変更される前の値後の値を比較して、それに基づいて処理を分けたい場合。
    • 特定のデータソースだけを監視したい場合。
    • 非同期処理のコールバックで、競合状態を防ぐために前の処理をキャンセルしたい場合(onInvalidate を活用)。
    • 監視対象の値が変更されたときにだけ実行したいが、初期実行は不要な場合 (デフォルト動作)。
  • watchEffect を使うべき場合:

    • 特定の副作用(DOM更新、ロギングなど)を実行するために、その副作用関数内で使用されるすべてのリアクティブな依存関係を自動的に追跡したい場合。
    • コールバックが常に初期値で実行される必要があり、その後のすべての依存関係の変更にも反応してほしい場合。
    • 古い値と比較する必要がない場合。
    • 非同期処理のリソース(タイマー、イベントリスナー、購読など)のクリーンアップが必要な場合(返り値の関数を活用)。

簡単に言えば、watch は「この特定のソースが変更されたら、このロジックを実行する」という意図を明確に表現したい場合に適しています。一方、watchEffect は「このロジックを実行するために必要なリアクティブなデータはこれらで、それらのどれかが変更されたらロジックを再実行する」という意図を表現したい場合に適しています。

多くの場合、どちらを使っても同じ結果を得ることは可能ですが、コードの意図をより明確にし、保守性を高めるために、適切に使い分けることが重要です。例えば、あるコンポーネントの表示/非表示状態とデータ配列の内容に基づいて、DOM 要素の属性を更新するといったシナリオでは、watchEffect でそれらのリアクティブな状態を副作用関数内でアクセスする方が、依存関係の自動追跡によりコードがシンプルになることがあります。一方、ユーザー入力に基づいて非同期 API 呼び出しを行い、前のリクエストをキャンセルしたい場合は、watchonInvalidate を使う方が意図が明確になり、制御しやすくなります。

まとめ

本記事では、Vue.js における watch プロパティおよび関数について、その基本的な使い方からOptions API と Composition API の違い、豊富なオプション(immediate, deep, flush)、複数ソースの監視方法、ウォッチャーの停止方法、そして実践的な活用例まで、詳細に解説しました。

watch は、リアクティブなデータソースの変更に反応して副作用を実行するための強力で柔軟なツールです。非同期処理のトリガー、複雑な状態遷移に伴う UI 更新、外部システムとの連携など、様々なシナリオでその能力を発揮します。

しかし、その柔軟性の高さゆえに、乱用するとコードが読みにくくなったり、パフォーマンス問題を引き起こしたりする可能性もあります。computed プロパティや watchEffect 関数といった Vue の他のリアクティビティ機能との違いを理解し、それぞれの目的と特性に合わせて適切に使い分けることが重要です。特に、派生的な値を計算するだけであれば computed を、副作用関数内のすべてのリアクティブな依存関係を自動追跡したい場合は watchEffect を検討し、明示的に特定のソースの変更を監視して副作用を実行したい場合に watch を選択するという指針を持つと良いでしょう。

また、deep オプションの使用によるパフォーマンス影響、不要になったウォッチャーの適切な停止によるメモリリーク防止など、注意すべき点もいくつか存在します。これらのベストプラクティスを守ることで、watch を効果的に、そして安全に活用し、より堅牢で保守性の高い Vue アプリケーションを開発することができます。

本ガイドを通じて watch の理解を深め、あなたの Vue 開発における強力な味方として活用できるようになることを願っています。Vue のリアクティビティシステムと watch を深く理解することは、Vue エキスパートへの重要な一歩となるでしょう。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール