はい、承知いたしました。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 には、この目的のためにいくつかのメカニズムが用意されています。
-
算出プロパティ (
computed
):- 一つまたは複数のリアクティブなデータソースに依存して、新しい派生的な値を生成するために使用されます。
- 依存するデータが変更されたときに自動的に再評価され、その結果がキャッシュされます。
- 副作用のない、値を返す同期的な計算に適しています。
- テンプレート内で複雑な式を書く代わりに使われます。
-
watch
プロパティ / 関数 (watch
):- 特定のリアクティブなデータソースの変更を監視し、データが変更されたときに副作用のある処理を実行するために使用されます。
- 非同期処理、DOM 操作、API 呼び出し、ログ出力など、値を返さない、または複雑なロジックを含む処理に適しています。
computed
とは異なり、デフォルトでは値をキャッシュしません(ただし、Options API の場合は関数形式でキャッシュのような振る舞いをさせることができますが、Composition API のwatch
関数は基本的に副作用トリガーです)。
computed
と watch
の使い分け
これは 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
では、以下の種類のデータを監視できます。
-
プリミティブ型 (String, Number, Booleanなど):
- 値そのものの変更を検知します。
- 上記の
message
やcount
の例のように、プロパティ名をキーに指定します。
-
オブジェクトまたは配列:
- デフォルトでは、オブジェクトや配列そのものへの再代入(参照の変更)のみを検知します。
- オブジェクトのプロパティ値や配列の要素の変更といった、内部の変更を検知するには、後述する
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
: 監視対象です。以下のいずれかを指定できます。- リアクティブなデータソース(
ref
、reactive
、computed
)。 - 値を返すゲッター関数。
- 上記を複数含む配列。
- リアクティブなデータソース(
callback
: 監視対象のデータが変更されたときに実行されるコールバック関数です。通常、(newValue, oldValue)
を引数に取ります。options
(省略可能): オプションオブジェクトです (immediate
,deep
,flush
など)。
監視対象の種類 (Composition API)
Composition API の watch
関数は、より柔軟にさまざまな種類のデータを監視できます。
-
ref
:ref
を直接source
として渡すと、その.value
の変更が監視されます。
javascript
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log('count.value が変更されました:', oldValue, '->', newValue);
}); -
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
オブジェクト全体を監視する場合、newValue
とoldValue
は同じプロキシオブジェクトへの参照になります。変更前の値が必要な場合は、コールバック内で古い値を保持するなどの工夫が必要です。しかし、通常は特定のプロパティや関数ゲッターを監視することが多いです。 -
ゲッター関数:
- 監視したい値を返す関数を
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 を指定
);
“`
ゲッター関数を使うことで、リアクティブなデータソースから派生した任意の値を監視できます。これは非常に強力で柔軟な機能です。 - 監視したい値を返す関数を
-
配列:
- 複数の監視対象を指定したい場合は、これらを配列として
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
関数の第三引数としてオブジェクト形式で指定します。
主なオプションは以下の通りです。
immediate
: コールバックを監視開始時に即座に一度実行するかどうか。deep
: オブジェクトや配列の内部の変更も検知するかどうか。flush
: コールバック関数の実行タイミングを指定します(Vue の更新サイクルのどこで実行されるか)。'pre'
,'post'
,'sync'
のいずれか。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
};
}
};
``
watch
Composition API では、関数の第三引数として
{ 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 の watch
で deep: true
と handler
を使う場合、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
};
}
};
``
watch
Composition API ので
deep: trueを使う場合、
newValueと
oldValue` は同じ 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
Count: {{ count }}
``
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
};
}
}
}
};
``
get()
Options API でゲッター関数 (メソッド) と
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
};
}
};
``
newValue
Composition API で複数ソースを配列として監視する場合、コールバック関数の引数と
oldValue` は、それぞれ監視対象のソースに対応する値の配列になります。これにより、どのソースが変更されたかにかかわらず、単一のコールバックでまとめて変更を処理できます。
この配列形式の監視は、複数の入力フィールドの値を組み合わせてバリデーションを行う、複数のフィルター条件が変更されたときにリストを再取得する、といったシナリオで非常に有用です。
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++;
}
}
};
``
watch
Options API のプロパティで定義されたウォッチャーは、コンポーネントの
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
};
}
};
``
setup()
Composition API の関数内で同期的に定義された
watchウォッチャーは、その
setupが属するコンポーネントインスタンスがアンマウントされるときに自動的に停止されます。これは
onUnmounted` ライフサイクルフックに自動的に登録されるのと同等です。したがって、ほとんどの場合、明示的に停止関数を呼び出す必要はありません。
ただし、以下のようなケースでは明示的な停止が必要です。
- 特定の条件が満たされたときに早期にウォッチャーを停止したい場合(上記の例)。
- 非同期処理(
setTimeout
,fetch
のコールバックなど)の中でwatch
を定義した場合、そのウォッチャーは特定のコンポーネントインスタンスに関連付けられない可能性があるため、手動で停止する必要があります。 watch
ウォッチャーがコンポーネントの外部(例えば、Vue Router のナビゲーションガードや、Vuex ストアの購読)で定義されている場合、手動でクリーンアップが必要です。
メモリリーク防止の重要性
不要になったウォッチャーを適切に停止することは、メモリリークを防ぐ上で非常に重要です。停止しないウォッチャーは、コンポーネントが DOM から削除された後も存在し続け、監視対象のデータへの参照を保持するため、ガベージコレクションの対象とならず、メモリを消費し続ける可能性があります。特に、頻繁に生成/破棄されるコンポーネントや、 SPA でページ遷移が多いアプリケーションでは、注意が必要です。Vue の自動クリーンアップ機能は便利ですが、手動で設定した監視や、ライフサイクル外で設定した監視については、開発者が責任を持って停止する必要があります。
watch
の実践的な活用例
watch
は、その柔軟性から幅広いシナリオで活用できます。ここでは、いくつかの実践的な活用例を紹介します。
1. 非同期処理のトリガー (API呼び出し)
最も一般的な watch
の活用例の一つは、特定のデータの変更をトリガーとして非同期処理(特にサーバーへのAPI呼び出し)を実行することです。
例えば、検索入力フィールドの値が変更されたときに、検索結果を非同期で取得するシナリオです。
Composition API (<script setup>
) での例
“`html
Loading…
- {{ result.name }}
- No results found.
``
searchText
この例では、の変更を
watchし、変更があるたびに
fetchSearchResults関数(非同期関数)を実行しています。コールバック関数に
asyncを付けることで、非同期処理を
await` で待つことができます。
補足: Debounce と AbortController
ユーザーが検索クエリを素早く入力する場合、一文字入力するたびに watch
コールバックが実行され、API呼び出しが頻繁に行われる可能性があります。これはサーバーに負荷をかけたり、不要なネットワークリクエストを発生させたりするため、多くの場合望ましくありません。
これを避けるためには、debounce という手法がよく用いられます。これは、イベント(ここでは入力値の変更)が一定時間発生しなかった場合にのみ、指定された処理を実行するものです。lodash
の debounce
関数などを利用できます。
“`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
``
email
この例では、と
passwordの変更をそれぞれ
watchし、コールバック関数内でバリデーションロジックを実行して、対応するエラーメッセージ用の
refを更新しています。
immediate: true` を使用して、コンポーネント表示時に初期値に対してもバリデーションが行われるようにしています。
3. URLパラメータの変更に応じたデータ取得
シングルページアプリケーション (SPA) では、URL のパラメータ(クエリパラメータやパスパラメータ)が変更されたときに、それに基づいて表示内容を更新したり、データを再取得したりすることがよくあります。Vue Router を使用している場合、現在のルート情報($route
プロパティまたは useRoute
コンポーザブル)はリアクティブなので、watch
を使って監視できます。
Composition API (<script setup>
) と Vue Router での例
```html
Post Details
Loading post ID: {{ postId }}...
{{ post.title }}
{{ post.body }}
Post not found.
``
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-if
や v-bind:class
で十分ですが、状態の変化が複雑な条件に依存する場合や、それに伴う副作用(例: モーダルウィンドウを開く/閉じる)が必要な場合は、watch
が有用です。
```html
Count: {{ count }}
``
showModal
この例では、の変更を監視し、それに合わせて
document.bodyにクラスを付与/削除することで、モーダル表示時の背景スクロールを制御しています。また、
countが特定の閾値を超えたら
showModalを
trueにするという、別の状態に基づいた
showModalの更新も行っています。このように、
watch` は状態遷移に伴う副作用を管理するのに役立ちます。
watch(count, ...)
のような、ある値が変更されたときに別の値を更新するようなロジックは、場合によっては computed
のセッターや、リアクティブなデータ更新関数の中で直接行うこともできますが、watch
を使うことで変更の「反応」として明確に表現できます。
watch
を使う上での注意点とベストプラクティス
watch
は非常に強力で柔軟なツールですが、その性質上、コードが複雑になりやすく、パフォーマンス問題を引き起こす可能性もあります。効果的に、そして安全に watch
を使用するために、以下の注意点とベストプラクティスを考慮しましょう。
-
副作用を伴う処理に使う:
watch
は、データ変更に応じて「何かを行う」ためのものです。具体的には、API呼び出し、DOM操作、外部ライブラリとの連携、ロギングなど、値を返すことだけが目的ではない副作用を伴う処理に適しています。派生的な値を計算するだけであれば、computed
を使うべきです。 -
computed
との使い分けを明確にする:
「算出 vs 監視」のセクションで述べたように、computed
とwatch
は目的が異なります。これを混同すると、コードが非効率になったり、意図しない挙動を引き起こしたりします。新しい値を生成するならcomputed
、副作用をトリガーするならwatch
と覚えましょう。 -
パフォーマンスへの影響を考慮する (特に
deep
):
deep: true
オプションは便利ですが、大規模なオブジェクトや配列に対して使用すると、変更検出のコストが高くなり、アプリケーションの応答性が低下する可能性があります。必要な場合にのみdeep
を使用し、可能であればゲッター関数で特定のネストされたプロパティのみを監視したり、データ構造をフラット化したりすることを検討してください。 -
複雑になりすぎないようにする:
一つのwatch
コールバック内で複数の複雑な処理を行ったり、複数のwatch
が互いに連鎖的にトリガーされるような設計は避けるべきです。コードが追いにくくなり、予期しないループや無限更新を引き起こす可能性があります。関連するロジックは関数に切り出し、watch
のコールバックはできるだけシンプルに保つように心がけましょう。 -
監視対象を絞る:
必要以上に広い範囲のデータを監視しないようにしましょう。オブジェクト全体や配列全体をdeep
監視する前に、本当に必要なのはその中の特定のプロパティの変更だけではないか検討してください。ゲッター関数を使うことで、監視対象を必要な値に絞り込むことができます。 -
適切な
flush
タイミングを選ぶ:
DOM アクセスを必要とする処理を行う場合は、必ずflush: 'post'
オプションを使用してください。デフォルトの'pre'
タイミングでは、DOM がまだ更新されていないため、古い DOM 構造を操作してしまう可能性があります。 -
不要になったウォッチャーを停止する:
コンポーネントのライフサイクルとは無関係に定義されたウォッチャー(例: グローバルイベントリスナー、非同期処理内のwatch
)や、特定の条件が満たされたら役割を終えるウォッチャーは、明示的に停止することを忘れないでください。Composition API のwatch
が返す停止関数や、Options API の$watch
が返す停止関数を活用し、メモリリークを防ぎましょう。 -
watchEffect
と比較検討する:
次に説明するwatchEffect
は、依存関係を自動的に追跡するという点でwatch
とは異なります。どちらを使うべきか迷う場合は、以下の点を考慮してください。- 古い値と新しい値を比較して処理を分けたい場合は
watch
。 - 非同期処理などで、前の処理をキャンセルしたい場合は
watch
とAbortController
。 - 依存関係が明確で、コールバックが実行されるたびにすべての依存関係の最新値を使って処理を実行したい場合は
watchEffect
。 - 初期実行が常に必要な場合は
watchEffect
(またはwatch
withimmediate: true
)。 - 監視対象を明示的に指定したい場合は
watch
。
- 古い値と新しい値を比較して処理を分けたい場合は
これらの注意点とベストプラクティスを守ることで、watch
を効果的かつ安全に活用し、堅牢で保守性の高い Vue アプリケーションを構築できます。
watchEffect
との比較
Vue 3 の Composition API には、watch
関数の他に watchEffect
という類似の関数があります。これらはどちらもリアクティブなデータ変更に反応して副作用を実行するために使用されますが、その動作原理と使い分けのポイントが異なります。
watchEffect
とは
watchEffect
関数は、副作用関数を受け取ります。この関数の中でリアクティブなデータソース(ref
, reactive
, computed
)がアクセスされると、Vue は自動的にそれらを依存関係として追跡します。その後、追跡された依存関係のいずれかが変更されるたびに、副作用関数が再実行されます。
watchEffect
は、watch
の immediate: 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
};
}
};
```
watch
と watchEffect
の違い
特徴 | watch 関数 |
watchEffect 関数 |
---|---|---|
監視対象の指定 | 明示的に指定 (source 引数) |
副作用関数内でアクセスされたリアクティブデータ |
依存関係の収集 | source 引数で指定されたもの |
副作用関数の最初の実行中に自動収集 |
コールバックの引数 | (newValue, oldValue) |
なし (最新の値は副作用関数内で直接アクセス) |
初期実行 (immediate ) |
オプション (immediate: true ) で制御 |
常に即座に実行される (最初の依存関係収集も兼ねる) |
古い値へのアクセス | oldValue 引数で容易 |
コールバック内で手動で保持する必要がある |
特定の条件での再実行 | ソースの値が実際に変更されたとき | 追跡された依存関係のいずれかが変更されたとき |
クリーンアップ | コールバック関数から onInvalidate を呼び出す |
副作用関数からクリーンアップ関数を返す |
クリーンアップ関数の違い
watch
と watchEffect
は、非同期処理などを行う際に、不要になった処理をキャンセルするためのクリーンアップ方法が異なります。これは、前の変更に対する非同期処理が完了する前に新しい変更が発生した場合に、競合状態を防ぐために重要です。
-
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` のクリーンアップは、副作用関数が返す関数として定義されるため、非同期処理のキャンセルだけでなく、イベントリスナーの解除、タイマーのクリア、購読の解除など、様々なリソースクリーンアップに利用できます。
watch
と watchEffect
の使い分け
-
watch
を使うべき場合:- リアクティブなデータが変更される前の値と後の値を比較して、それに基づいて処理を分けたい場合。
- 特定のデータソースだけを監視したい場合。
- 非同期処理のコールバックで、競合状態を防ぐために前の処理をキャンセルしたい場合(
onInvalidate
を活用)。 - 監視対象の値が変更されたときにだけ実行したいが、初期実行は不要な場合 (デフォルト動作)。
-
watchEffect
を使うべき場合:- 特定の副作用(DOM更新、ロギングなど)を実行するために、その副作用関数内で使用されるすべてのリアクティブな依存関係を自動的に追跡したい場合。
- コールバックが常に初期値で実行される必要があり、その後のすべての依存関係の変更にも反応してほしい場合。
- 古い値と比較する必要がない場合。
- 非同期処理のリソース(タイマー、イベントリスナー、購読など)のクリーンアップが必要な場合(返り値の関数を活用)。
簡単に言えば、watch
は「この特定のソースが変更されたら、このロジックを実行する」という意図を明確に表現したい場合に適しています。一方、watchEffect
は「このロジックを実行するために必要なリアクティブなデータはこれらで、それらのどれかが変更されたらロジックを再実行する」という意図を表現したい場合に適しています。
多くの場合、どちらを使っても同じ結果を得ることは可能ですが、コードの意図をより明確にし、保守性を高めるために、適切に使い分けることが重要です。例えば、あるコンポーネントの表示/非表示状態とデータ配列の内容に基づいて、DOM 要素の属性を更新するといったシナリオでは、watchEffect
でそれらのリアクティブな状態を副作用関数内でアクセスする方が、依存関係の自動追跡によりコードがシンプルになることがあります。一方、ユーザー入力に基づいて非同期 API 呼び出しを行い、前のリクエストをキャンセルしたい場合は、watch
と onInvalidate
を使う方が意図が明確になり、制御しやすくなります。
まとめ
本記事では、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 エキスパートへの重要な一歩となるでしょう。