methods, computed, watchの違い
Vue.jsには状態の扱い方として methods 、 computed 、 watch の3つの主要なオプションがあります。それぞれの特徴をまずは理解します。
| オプション | 主な役割 | タイミング | 特徴 |
|---|---|---|---|
methods | 関数として呼び出す処理 | 明示的に呼び出されたとき | データを変更したり処理したりするための通常の関数 |
computed | 計算結果を自動で返す | 依存するデータが変化したときに自動再評価 | キャッシュ付きのリアクティブ計算プロパティ。表示用などに最適 |
watch | 値の変化を監視して処理を実行 | 監視対象のデータが変わった瞬間に実行される副作用 | API呼び出しやログ出力など、処理をトリガーさせたいときに使う |

methods
- 関数を定義する場所
- イベントハンドラや任意のタイミングで実行したい処理を記述
- 呼び出されるたびに処理が実行される
- 引数を渡すことができる
computed
- 算出プロパティ(依存するデータが変更された時のみ再計算)
- キャッシュされる(依存関係が変わらない限り再計算しない)
- テンプレート内で{{}}を使って表示するのに適している
- ゲッター関数として定義(setterも可能だが稀)
watch
- データの変化を監視して処理を実行
- 非同期処理や重い処理に向いている
- 変化前後の値を受け取れる
- 深い監視(deepオプション)も可能
演習問題 (24問)
初級問題 (6問)
- methodsを使って、ボタンクリックでカウンターを増加させるコードを書いてください。
- computedを使って、firstNameとlastNameからfullNameを算出するコードを書いてください。
- watchを使って、inputValueが変更されたときにコンソールにログを出力するコードを書いてください。
- methodsとcomputedのどちらを使うべきか、以下のシナリオで選択してください。
- フォームの送信処理
- フィルタリングされたリストの表示
- 以下のコードの欠点を指摘し、改善してください(computedを使うべき箇所)。
<template>
<div>{{ getFullName() }}</div>
</template>
<script>
export default {
data() {
return {
firstName: 'Taro',
lastName: 'Yamada'
}
},
methods: {
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
}
</script>
- watchとcomputedのどちらを使うべきか、以下のシナリオで選択してください。
- APIからデータを取得して表示
- 入力値に基づいて計算された値を表示
中級問題 (12問)
- 商品リストとフィルタ条件からフィルタリングされたリストを返すcomputedプロパティを書いてください。
- フォーム入力のバリデーションをwatchで実装してください(email形式のチェック)。
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- ディレバウンス処理をwatchで実装してください(入力後500ms経ってから処理実行)。
ディレイバウンス処理とは、メール送信時に一時的なエラーが発生した場合、システムが自動的に再送を試みる処理のことです。
- computedのsetterを使って、fullNameを分割してfirstNameとlastNameに代入するコードを書いてください。
- deep watchを使って、オブジェクトのネストされたプロパティの変更を監視するコードを書いてください。
- 複数のデータソースを監視するwatchを書いてください(firstNameとlastNameの両方の変更を監視)。
- 以下のコードで、なぜcomputedが適切なのか説明してください。
computed: {
totalPrice() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
- watchのimmediateオプションを使うシナリオを説明し、コード例を示してください。
- 非同期処理を含むcomputedプロパティはなぜ避けるべきか説明してください。
- コンポーネントのpropsをwatchで監視するコードを書いてください。
- 以下のシチュエーションでmethods, computed, watchのどれを使うべきか選択し、その理由を説明してください:
- ウィンドウのリサイズイベントを処理
- ショッピングカートの合計金額を計算
- 検索クエリが変更されたときにAPIを呼び出す
- 動的に計算されるcomputedプロパティ名を使ったコードを書いてください。
上級問題 (6問)
- Vuexのstateの変更をwatchで監視し、変更があった場合にローカルデータを更新するコードを書いてください。
- パフォーマンスの観点から、大きなリストのフィルタリングをcomputedで実装する際の注意点を説明してください。
- watchEffectとwatchの違いを説明し、適切な使用例を示してください。
- コンポーネントのライフサイクルとwatchの初期化タイミングに関連する潜在的な問題とその解決策を説明してください。
- 複雑なオブジェクトの変更検知において、JSON.stringifyを使ったdeep watchの代替方法とその利点を説明してください。
- TypeScriptを使用した場合のcomputedプロパティの型定義方法を示し、ジェネリック型を使った例を書いてください。
解答例
初級問題解答
- methodsを使って、ボタンクリックでカウンターを増加させるコード
<template>
<button @click="increment">Count: {{ count }}</button>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
}
}
}
</script>
- computedを使って、firstNameとlastNameからfullNameを算出するコード
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
- watchを使って、inputValueが変更されたときにコンソールにログを出力するコード
watch: {
inputValue(newVal, oldVal) {
console.log(`Input changed from ${oldVal} to ${newVal}`);
}
}
- methodsとcomputedのどちらを使うべきか
- フォームの送信処理 => methods
- フィルタリングされたリストの表示 => computed
- コードの欠点とcomputedを使い改善
<template>
<div>{{ fullName }}</div>
</template>
<script>
export default {
data() {
return {
firstName: 'Taro',
lastName: 'Yamada'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
}
</script>
- watchとcomputedのどちらを使うべきか
- APIからデータを取得して表示 => watch
- 入力値に基づいて計算された値を表示 => computed
中級問題解答
- 商品リストとフィルタ条件からフィルタリングされたリストを返すcomputedプロパティ
computed: {
filteredProducts() {
return this.products.filter(product => {
return product.price <= this.maxPrice && product.category === this.selectedCategory;
});
}
}
- email形式のチェックバリデーションをwatchで実装
watch: {
email(newVal) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
this.emailValid = regex.test(newVal);
}
}
- ディレバウンス処理をwatchで実装
watch: {
searchQuery: {
handler(newVal) {
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
this.search();
}, 500);
}
}
}
- computedのsetterを使って、fullNameを分割してfirstNameとlastNameに代入するコード
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1] || '';
}
}
}
- deep watchを使って、オブジェクトのネストされたプロパティの変更を監視するコード
watch: {
user: {
handler(newVal) {
console.log('User data changed');
},
deep: true
}
}
- firstNameとlastNameの両方の変更を監視をwatchで実装
watch: {
firstName: 'nameChanged',
lastName: 'nameChanged'
},
methods: {
nameChanged() {
console.log('Name changed');
}
}
- computedが適切な理由
- itemsが変更された時のみ再計算され、キャッシュされる
- テンプレート内でシンプルに{{ totalPrice }}と書ける
- 算出ロジックがコンポーネントから分離されている
- immediateオプションの使用例
コンポーネント作成時にもhandlerを実行したい場合に使用から
watch: {
propValue: {
handler(newVal) {
this.initialize(newVal);
},
immediate: true
}
}
- computedは同期的であるべき理由
- computedの値は他のcomputedやテンプレートから依存される可能性がある
- 非同期処理だと値が確定するタイミングが不明確になる
- 代わりにwatch + dataプロパティの組み合わせを使うべき
- コンポーネントのpropsをwatchで監視するコード
watch: {
'user.id': {
handler(newVal) {
this.fetchUserData(newVal);
}
}
}
- methods, computed, watchのどれを使うべきか
- ウィンドウのリサイズイベントを処理 => methods (イベントハンドラ)
- ショッピングカートの合計金額を計算 => computed (依存データから算出)
- 検索クエリが変更されたときにAPIを呼び出す => watch (非同期処理)
- 動的に計算されるcomputedプロパティ名を使ったコード
computed: {
[`${this.prefix}Value`]() {
return this.calculateValue();
}
}
上級問題解答
- Vuexのstateの変更をwatchで監視し、変更があった場合にローカルデータを更新するコード
watch: {
'$store.state.user': {
handler(newVal) {
this.localUser = { ...newVal };
},
deep: true
}
}
- 大きなリストのフィルタリングをcomputedで実装する際の注意点
大きなリストのフィルタリング注意点:
- フィルタ条件を最小限にする
- 可能なら事前にソートしておく
- ページネーションや仮想スクロールを検討
- 重い処理ならWeb Workerを検討
- watchEffectとwatchの違いと適切な使用例
- watchEffect => 即時実行、依存関係を自動追跡
- watch => 明示的に監視対象を指定、変更前後の値取得可能
// 自動依存追跡 watchEffect
watchEffect(() => {
console.log(this.count + this.offset);
});
// 明示的監視 watch
watch(
() => this.count,
(newVal, oldVal) => {
// 処理
}
)
- ライフサイクルとwatchの問題点
- createdフックでデータ取得する場合、watchの初期化前にデータがセットされる可能性あり
- immediateオプションか、createdで直接メソッド呼び出しを検討
- JSON.stringifyの代替
watch: {
obj: {
handler(newVal, oldVal) {
if (!this.isEqual(newVal, oldVal)) {
// 処理
}
},
deep: true
}
}
利点: パフォーマンス向上、不要なトリガー防止
- TypeScriptのcomputed型定義
import { defineComponent } from 'vue';
export default defineComponent({
computed: {
fullName(): string {
return `${this.firstName} ${this.lastName}`;
},
// ジェネリック型
itemsCount(): number {
return this.items.length;
}
}
});