初心者向け Vue computed の使い方・基礎を徹底解説

はい、承知いたしました。
Vue.jsの初心者向けに、computedプロパティの使い方と基礎を徹底的に解説する約5000語の記事を作成します。記事は直接表示します。


初心者向け Vue computed の使い方・基礎を徹底解説

Vue.jsでの開発を進める上で、computedプロパティは非常に強力で、かつ頻繁に利用される機能の一つです。特に、複数のデータを組み合わせて表示したり、リストを加工して表示したりする場合に、コードをより効率的、かつ分かりやすく記述するために役立ちます。

この記事は、Vue.jsを学び始めたばかりの初心者の皆さんに向けて、computedプロパティとは何か、なぜそれが必要なのか、そして具体的にどのように使うのかを、基本的な概念から実践的な応用例まで、ステップバイステップで徹底的に解説することを目的としています。

この記事を読むことで、あなたは以下のことを習得できます。

  • Vue.jsのリアクティブシステムにおけるcomputedプロパティの役割を理解する。
  • computedプロパティとmethodsプロパティの最も重要な違い(キャッシングと依存関係の追跡)を明確に理解し、適切に使い分けられるようになる。
  • 簡単な計算から、リストのフィルタリング、ソートといった複雑な処理まで、computedプロパティを使って実現する方法を学ぶ。
  • computedプロパティを使う上での注意点やベストプラクティスを知る。

Vue.jsの世界へようこそ!それでは、computedプロパティの魅力に迫っていきましょう。

1. Vue.jsの基本おさらい:データ、テンプレート、メソッド

computedプロパティの話に入る前に、Vue.jsの基本的な要素である「データ」「テンプレート」「メソッド」について簡単におさらいしておきましょう。これらの要素がどのように連携し、なぜcomputedが必要になるのかを理解する上で重要です。

1.1 リアクティブデータ (dataプロパティ)

Vue.jsの最も基本的な特徴の一つは「リアクティブシステム」です。これは、JavaScriptのデータを変更すると、それに紐づけられたHTML(テンプレート)が自動的に更新される仕組みのことです。

コンポーネントのデータは、通常、dataオプション内で関数として定義し、その関数がオブジェクトを返す形式で記述します。このオブジェクトのプロパティが、コンポーネントの持つリアクティブな状態(データ)となります。

javascript
// 基本的なVueコンポーネントの定義例
export default {
data() {
return {
message: 'こんにちは、Vue!', // 文字列データ
counter: 0, // 数値データ
isLoggedIn: false // 真偽値データ
}
}
}

これらのmessagecounterisLoggedInといったデータが変更されると、そのデータが表示されているテンプレートの部分が自動的に更新されます。

1.2 テンプレート構文 (データの表示、ディレクティブ)

Vue.jsのテンプレートは、HTMLをベースに、Vue独自の拡張構文(ディレクティブ)が追加されたものです。テンプレートを使って、dataプロパティで定義したデータを表示したり、ユーザーの操作に応じた条件分岐や繰り返し処理を行ったりします。

  • データの表示(Mustache構文 {{ }}):
    {{ dataプロパティ名 }} の形式で、データを直接テンプレートに埋め込みます。

    html
    <p>{{ message }}</p>
    <p>現在のカウンター: {{ counter }}</p>

  • ディレクティブ (v- で始まる属性):
    特定の振る舞いを要素に追加する際に使用します。代表的なものには以下があります。

    • v-bind: HTML属性にデータをバインドする(例: :hrefv-bind:href の省略形)
    • v-if, v-else-if, v-else: 条件付きレンダリング
    • v-for: リストレンダリング
    • v-on: イベントハンドリング(例: @clickv-on:click の省略形)
    • v-model: フォーム入力要素とデータを双方向バインド

    “`html

    • {{ item.text }}

    “`

これらの基本的なテンプレート構文とリアクティブデータのおかげで、Vue.jsでは宣言的にUIを構築できます。「データがこうなったら、UIはこう表示される」という形で記述すれば、データの変化に応じてUIが自動的に追従してくれます。

1.3 メソッド (methodsプロパティ)

methodsプロパティは、コンポーネント内で実行したい関数を定義する場所です。これらの関数は、イベントハンドラとして(例: ボタンクリック時)呼び出されたり、テンプレート内で直接呼び出されたりします。

javascript
export default {
data() {
return {
counter: 0
}
},
methods: {
increment() {
this.counter++; // データを変更する
},
decrement() {
this.counter--;
},
greet(name) {
alert('こんにちは、' + name + 'さん!');
}
}
}

テンプレートからは以下のように呼び出せます。

html
<button @click="increment">増やす</button>
<button @click="decrement">減らす</button>
<button @click="greet('太郎')">挨拶</button>

methodsは、ユーザーの操作に応じた処理や、特定のタイミングで実行したい手続き的なロジックを記述するのに適しています。例えば、「ボタンがクリックされたらカウンターを増やす」「フォームが送信されたらデータをサーバーに送信する」といった処理です。

1.4 なぜmethodsだけでは不十分なケースがあるのか? (computedへの導入)

さて、基本的なデータ、テンプレート、メソッドについておさらいしましたが、ここで一つの疑問が生まれるかもしれません。

「データを加工して表示したいとき、methodsで関数を作って、テンプレートでその関数を呼び出せばいいのではないか?」

例えば、ユーザーの「名」と「姓」というデータがあって、テンプレートに「フルネーム」を表示したいとします。

javascript
export default {
data() {
return {
firstName: '太郎',
lastName: '山田'
}
},
methods: {
getFullName() {
return this.lastName + ' ' + this.firstName;
}
}
}

テンプレートでは、以下のようにメソッドを呼び出すことができます。

“`html

氏名: {{ getFullName() }}

“`

これは確かに動作します。しかし、このアプローチにはいくつかの欠点があります。特に「パフォーマンス」と「可読性」の面で問題が生じることがあります。

  1. パフォーマンスの問題 (Methodsは毎回実行される):
    {{ getFullName() }} のようにテンプレート内でメソッドを呼び出すと、そのコンポーネントが再レンダリングされるたびに、このメソッドが毎回実行されます。Vue.jsは、依存するデータが変更されたときにコンポーネントの一部または全体を再レンダリングしますが、この再レンダリングのたびにメソッドは無条件に呼び出されてしまうのです。もし、そのメソッドの計算コストが高かったり、テンプレート内で頻繁に呼び出されたりする場合、これはパフォーマンスの低下を招く可能性があります。

  2. 可読性の問題 (テンプレートが複雑になる):
    簡単な計算であればテンプレート内で直接式を書くことも可能ですが、少し複雑な計算やデータ加工が必要になると、テンプレート内の式が非常に長くなり、読みにくくなってしまいます。

    html
    <!-- 可読性が低い例 -->
    <p>合計金額 (税込み): {{ (item.price * item.quantity * 1.10).toFixed(2) }}円</p>

    このような計算ロジックをテンプレートから切り離し、コンポーネントのJavaScript部分にまとめた方が、コードの見通しが良くなります。

これらの問題を解決し、データを加工して表示する際に、より効率的で分かりやすい方法を提供してくれるのが、これから解説するcomputedプロパティです。

2. computedとは何か?

Vue.jsにおけるcomputedプロパティは、コンポーネントのdataや他のcomputedプロパティに基づいて、計算された値を提供するものです。これは、テンプレート内でデータを加工して表示したい場合や、複数のデータから新しいデータを生成したい場合に特に役立ちます。

computedプロパティは、computedオプション内で定義します。各computedプロパティは、通常、値を返す関数として定義されます。この関数は、その計算に必要なデータ(依存データ)が変更されたときにのみ再実行され、計算結果はキャッシュされます。

基本的な構文は以下の通りです。

javascript
export default {
data() {
return {
// ... 依存するデータ ...
}
},
computed: {
// computedプロパティ名: function() { ... 計算ロジック ... return 計算結果; }
computedPropertyName: function() {
// ここで data や他の computed プロパティを使って計算を行う
// この関数内で参照した data や computed プロパティが「依存データ」となる
return '計算された値';
}
}
}

テンプレート内では、computedプロパティはまるでdataプロパティであるかのように、プロパティとして参照します(関数のように()をつけて呼び出す必要はありません)。

“`html

{{ computedPropertyName }}

“`

これが非常に重要なポイントです。関数呼び出しではなく、プロパティ参照のように記述することで、Vue.jsは内部的にそのcomputedプロパティが「計算された値」であることを認識し、特別な最適化(キャッシング)を適用することができます。

簡単なコード例:氏名と名からフルネームを生成

先ほどのメソッドの例を、computedプロパティを使って書き直してみましょう。

javascript
export default {
data() {
return {
firstName: '太郎',
lastName: '山田'
}
},
// computed プロパティを定義する
computed: {
// fullName という名前の computed プロパティ
fullName: function() {
console.log('fullName computed が実行されました'); // 実行タイミングを確認するためのログ
// この計算は firstName と lastName に依存する
return this.lastName + ' ' + this.firstName;
}
}
}

テンプレートでは、これをプロパティとして参照します。

“`html

氏名: {{ fullName }}



“`

このコードを実行し、firstNamelastNameの値を変更してみてください。コンソールにはfullName computed が実行されましたというログが出力され、テンプレートの氏名表示が自動的に更新されることが確認できます。

一方、firstNamelastNameを変更せずに、コンポーネントの別の部分で何か変更があり、再レンダリングが発生したとします(例えば、別のデータプロパティを変更するボタンをクリックするなど)。このとき、fullName computed が再計算されることはありません。ログが出力されないことで、キャッシングされていることが確認できます。

これが、computedプロパティの核となる仕組みであり、methodsとの決定的な違いです。

3. computedのメリットとmethodsとの比較

computedプロパティがなぜ有用なのか、そのメリットをmethodsとの比較を通してさらに詳しく見ていきましょう。主なメリットは以下の2点です。

  1. キャッシング (Caching): 依存データが変更されない限り、計算結果がキャッシュされ、再利用される。
  2. 依存関係の追跡 (Dependency Tracking): 依存するデータが変更されたときだけ自動的に再計算される。

3.1 キャッシング (Caching)

computedプロパティの最も重要な特徴は、その計算結果がキャッシュされるという点です。

これはどういうことでしょうか?

computedプロパティの関数は、初回アクセス時に実行され、その計算結果がVueインスタンス内に保存(キャッシュ)されます。次回以降にそのcomputedプロパティが参照されたとき、Vue.jsはまず、そのcomputedプロパティが依存しているデータ(dataプロパティや他のcomputedプロパティ)が前回の計算以降に変更されているかどうかを確認します。

  • 依存データが変更されていない場合: Vue.jsは計算を再実行せず、キャッシュされている前回の計算結果をそのまま返します。
  • 依存データが一つでも変更されている場合: Vue.jsはcomputedプロパティの関数を再実行し、新しい計算結果をキャッシュして返します。

このキャッシングの仕組みは、計算コストが高い処理を頻繁に表示する必要がある場合に、非常に高いパフォーマンスを発揮します。

対照的に、methodsプロパティ内の関数をテンプレートから呼び出した場合、そのコンポーネントが再レンダリングされるたびに、そのメソッドは無条件に実行されます。たとえそのメソッドが依存しているデータが何も変更されていなかったとしてもです。

キャッシングの具体例:

先ほどのfullNameの例で考えてみましょう。

javascript
export default {
data() {
return {
firstName: '太郎',
lastName: '山田',
message: 'これは別のデータです' // fullName とは無関係なデータ
}
},
computed: {
fullName: function() {
console.log('fullName computed が実行されました');
return this.lastName + ' ' + this.firstName;
}
},
methods: {
// message を変更するだけのメソッド
changeMessage() {
this.message = 'メッセージが変更されました: ' + new Date().getTime();
}
}
}

“`html

氏名: {{ fullName }}

{{ message }}



“`

このコードを実行し、以下の操作をしてみてください。

  1. ページを読み込む: fullName computed が実行されました と表示されます。
  2. changeMessage ボタンをクリックする: message が変更され、テンプレートが更新されます。しかし、fullName computed が実行されました は表示されません。これは、fullName が依存している firstNamelastName が変更されていないため、キャッシュされた結果が使われたからです。
  3. firstName の値を変更する: firstName が変更され、テンプレートが更新されます。このとき、fullName computed が実行されました と表示されます。これは、fullName が依存している firstName が変更されたため、再計算が行われたからです。

このように、computedは依存データが変更されたかどうかに応じて賢く計算をスキップします。これが、特にリストのフィルタリングやソートなど、ある程度計算コストがかかる処理をリアルタイムに反映させたい場合に、computedが推奨される大きな理由です。メソッドで同じ処理をテンプレートから呼び出すと、関連のないデータが変更されるたびに毎回フィルタリングやソートが実行され、パフォーマンスが大きく劣化する可能性があります。

3.2 依存関係の追跡 (Dependency Tracking)

computedプロパティのキャッシングが機能するためには、Vue.jsがそのcomputedプロパティが「何に依存しているか」を正確に知っている必要があります。これが「依存関係の追跡」と呼ばれる仕組みです。

Vue.jsは、computedプロパティの関数が初めて実行される際に、その関数内で参照されたリアクティブなデータ(dataプロパティや他のcomputedプロパティ)を自動的に記録します。これが、そのcomputedプロパティの「依存データ」リストになります。

その後、Vue.jsはこれらの依存データを監視します。依存データリストの中のどれか一つでも値が変更されると、Vue.jsはそのcomputedプロパティが無効になったと判断し、次にそのcomputedプロパティが参照されたときに必ず再計算を行うようにマークします。

この依存関係の追跡も、computedプロパティが非常に便利な理由です。開発者は、どのデータが変わったら計算結果が変わるかを意識して、手動で再計算のトリガーを設定する必要がありません。Vue.jsが自動的にそれを管理してくれます。宣言的に「この値は、これらのデータから計算される」と定義するだけで良いのです。

対照的に、methodsプロパティは依存関係を自動的に追跡しません。メソッドは単なる関数なので、いつ実行されるかは開発者がテンプレートやイベントハンドラで明示的に呼び出すかどうかに依存します。メソッド内で使用されるデータが変更されても、明示的にメソッドを呼び出さない限り、そのメソッドは実行されません。

依存追跡の具体例:

javascript
export default {
data() {
return {
items: [
{ id: 1, name: 'リンゴ', price: 100, quantity: 2 },
{ id: 2, name: 'バナナ', price: 50, quantity: 3 },
{ id: 3, name: 'オレンジ', price: 120, quantity: 1 }
]
}
},
computed: {
// 全アイテムの合計金額を計算する computed プロパティ
totalAmount: function() {
console.log('totalAmount computed が実行されました');
let total = 0;
for (const item of this.items) {
total += item.price * item.quantity;
}
return total;
},
// アイテム数を計算する computed プロパティ
itemCount: function() {
console.log('itemCount computed が実行されました');
return this.items.length;
}
},
methods: {
// 新しいアイテムを追加するメソッド
addItem() {
const newItem = { id: this.items.length + 1, name: '新しいアイテム', price: 200, quantity: 1 };
this.items.push(newItem); // items データが変更される
},
// 最初のアイテムの数量を変更するメソッド
increaseFirstItemQuantity() {
if (this.items.length > 0) {
this.items[0].quantity++; // items 配列内のオブジェクトの quantity プロパティが変更される
}
}
}
}

“`html

ショッピングカート

  • {{ item.name }} – {{ item.price }}円 x {{ item.quantity }}個

合計金額: {{ totalAmount }}円

アイテム数: {{ itemCount }}個


“`

この例で、以下の操作を試してください。

  1. ページ読み込み: totalAmount computed が実行されましたitemCount computed が実行されました が表示されます。
  2. addItem ボタンをクリック: items 配列に新しいアイテムが追加されます。items 配列そのものが変更されたため、totalAmountitemCount の両方が依存データ変更を検知し、両方とも再計算され、ログが表示されます
  3. increaseFirstItemQuantity ボタンをクリック: items 配列の最初の要素の quantity プロパティが変更されます。Vue.jsは配列の中のオブジェクトのプロパティ変更も追跡します。totalAmountitems 内の各アイテムの quantity に依存しているため、totalAmount は再計算され、ログが表示されます。しかし、itemCountitems 配列の length にのみ依存しており、length は変更されていないため、itemCount再計算されず、ログは表示されません。キャッシュされた値が使われます。

このように、computedプロパティは、自身の関数内で実際に参照したデータだけを賢く追跡し、必要なときにだけ再計算を行うのです。これは、Vue.jsのリアクティブシステムの強力な機能の一つです。

3.3 コードの可読性と宣言的プログラミング

もう一つの重要なメリットは、コードの可読性保守性の向上です。

テンプレート内で複雑なJavaScript式を書く代わりに、その計算ロジックをcomputedプロパティとしてコンポーネントのJavaScript部分に切り出すことで、テンプレートが非常にシンプルで読みやすくなります。テンプレートは「何を表示するか」に集中でき、計算やデータ加工の詳細はコンポーネントのロジック部分にまとめられます。

“`html

在庫のあるアイテムがあります。合計金額: {{ totalAmountInStock }}円

“`

このように、複雑な計算や条件を名前付きのcomputedプロパティにすることで、テンプレートを読む人がそのプロパティの意図をすぐに理解できるようになります。また、計算ロジックの変更が必要になった場合でも、テンプレートではなくJavaScript部分だけを修正すれば済むため、保守も容易になります。

これは、Vue.jsが推奨する「宣言的プログラミング」の考え方にも合致します。UIの見た目(テンプレート)はデータの状態に基づいて自動的に決定され、その状態をどのように加工・計算するかは、リアクティブなプロパティ(datacomputed)として宣言的に定義されるべき、という考え方です。

computedmethodsの使い分けまとめ

これまでの説明をまとめると、computedmethodsは以下の基準で使い分けるのが一般的です。

特徴 computedプロパティ methodsプロパティ
目的 依存データから計算されたを提供する 特定の処理を実行する関数を提供する
呼び出し方 プロパティとして参照 ({{ propName }}) 関数として呼び出し ({{ func() }})
キャッシング される (依存データが変わるまで) されない (呼び出されるたびに実行)
依存追跡 自動的に行われる 行われない
引数 基本的に取らない 取れる
副作用 避けるべき (純粋な計算) 許容される (データの変更、API呼び出しなど)

いつ computed を使うか:

  • 複数のデータプロパティから計算される値を表示したいとき。
  • リストデータをフィルタリングまたはソートして表示したいとき。
  • テンプレート内で複雑な式を使いたくないとき。
  • 計算結果をキャッシュしてパフォーマンスを向上させたいとき。
  • 引数が必要ない計算。

いつ methods を使うか:

  • ユーザーイベント(クリック、送信など)に応じて特定の処理を実行したいとき。
  • データを変更する処理。
  • API呼び出しなどの副作用を伴う処理。
  • 同じ計算ロジックを、異なる引数で複数回実行したいとき。
  • 計算結果をキャッシュする必要がない、またはするべきではないとき。

多くの場合、「データを加工してテンプレートに表示したい」という場面では、computedプロパティが第一の選択肢となるでしょう。特に、その計算結果がテンプレート内で頻繁に参照される場合は、キャッシングの恩恵を受けられるcomputedを使うべきです。

4. computedの様々な使い方と応用

computedプロパティの基本的な使い方とメリットを理解したところで、さらに様々な応用例を見ていきましょう。

4.1 複数のデータプロパティに依存する計算

最も基本的な使い方の繰り返しですが、複数のdataプロパティを組み合わせて新しい値を計算するのはcomputedの典型的なユースケースです。

例:商品の小計を計算

javascript
export default {
data() {
return {
price: 100, // 単価
quantity: 5 // 数量
}
},
computed: {
// price と quantity に依存する subtotal プロパティ
subtotal: function() {
return this.price * this.quantity;
}
}
}

“`html

単価: {{ price }}円

数量: {{ quantity }}個

小計: {{ subtotal }}円


“`

priceまたはquantityを変更すると、subtotalが自動的に再計算され、表示が更新されます。

4.2 他のcomputedプロパティに依存する計算

computedプロパティは、他のcomputedプロパティを依存データとして参照することもできます。これにより、計算ロジックをより小さな、再利用可能な単位に分割できます。

例:税込み合計金額を計算

上記の小計の例を拡張して、税率を考慮した税込み合計金額を計算してみましょう。

javascript
export default {
data() {
return {
price: 100,
quantity: 5,
taxRate: 0.10 // 税率 10%
}
},
computed: {
// price と quantity に依存
subtotal: function() {
console.log('subtotal computed が実行されました');
return this.price * this.quantity;
},
// subtotal と taxRate に依存
taxAmount: function() {
console.log('taxAmount computed が実行されました');
return this.subtotal * this.taxRate; // subtotal computed を参照
},
// subtotal と taxAmount に依存
totalAmount: function() {
console.log('totalAmount computed が実行されました');
return this.subtotal + this.taxAmount; // subtotal と taxAmount computed を参照
},
// 税込み合計金額を小数点以下2桁で表示するための computed
displayTotalAmount: function() {
console.log('displayTotalAmount computed が実行されました');
return this.totalAmount.toFixed(2); // totalAmount computed を参照
}
}
}

“`html

単価: {{ price }}円

数量: {{ quantity }}個

税率: {{ taxRate * 100 }}%

小計: {{ subtotal }}円

税額: {{ taxAmount }}円

合計金額 (税込み): {{ displayTotalAmount }}円



“`

この例では、totalAmountsubtotaltaxAmount に依存しています。また、taxAmountsubtotal に依存しています。そして subtotalpricequantity に依存しています。displayTotalAmounttotalAmount に依存しています。

pricequantity、またはtaxRateのいずれかを変更すると、関連するcomputedプロパティが連鎖的に再計算されます。しかし、例えばpriceだけを変更した場合、subtotaltaxAmounttotalAmountdisplayTotalAmountは再計算されますが、それ以外のcomputedプロパティ(もしあれば)は再計算されません。

このように、computedプロパティを組み合わせて使うことで、複雑な計算も段階的に記述でき、ロジックが追いやすくなります。

4.3 リストのフィルタリングやソート

配列データを加工して表示したい場合、computedプロパティが非常に役立ちます。特に、元のデータを変更せずに、表示上の形式だけを変えたい場合に適しています。

例:TODOリストのフィルタリング

未完了のTODOアイテムだけを表示するリストを作成してみましょう。

javascript
export default {
data() {
return {
todos: [
{ id: 1, text: '牛乳を買う', done: false },
{ id: 2, text: 'Vue.jsのcomputedを学ぶ', done: true },
{ id: 3, text: '夕食の準備', done: false },
{ id: 4, text: '散歩に行く', done: true }
],
hideCompleted: false // 完了済みのタスクを隠すかどうかを制御するデータ
}
},
computed: {
// hideCompleted の値に応じて、表示する todo リストをフィルタリングする computed プロパティ
filteredTodos: function() {
console.log('filteredTodos computed が実行されました');
if (this.hideCompleted) {
// 完了済みのものを除外
return this.todos.filter(todo => !todo.done);
} else {
// 全て表示
return this.todos;
}
},
// 未完了タスクの数を計算する computed プロパティ
remainingTasksCount: function() {
console.log('remainingTasksCount computed が実行されました');
// filter() で未完了のものだけを抽出し、その数を返す
return this.todos.filter(todo => !todo.done).length;
}
},
methods: {
// TODO の完了/未完了を切り替えるメソッド
toggleDone(todo) {
todo.done = !todo.done; // リアクティブな todo オブジェクトの done プロパティを変更
}
}
}

“`html

TODO リスト


  • {{ todo.text }}

未完了タスク数: {{ remainingTasksCount }}

“`

css
/* スタイルの例 */
.done {
text-decoration: line-through;
color: gray;
}

この例では、

  • filteredTodos というcomputedプロパティが、hideCompletedというデータと、元のtodos配列に依存しています。
  • hideCompletedの値が変わるか、todos配列の中身(要素の追加・削除や、要素のdoneプロパティの変更)が変わると、filteredTodosが再計算されます。
  • remainingTasksCounttodos 配列の中身(特に done プロパティ)に依存しており、todos の変更に応じて再計算されます。
  • テンプレートでは、元のtodos配列ではなく、加工済みのfilteredTodos配列をv-forで表示しています。

このように、computedを使うことで、元のデータを保ちつつ、表示形式を簡単に切り替えたり、加工したリストを表示したりできます。hideCompletedチェックボックスを操作してみてください。表示されるリストが動的に切り替わるのが分かります。

例:リストのソート

商品を価格の高い順や低い順にソートして表示する場合も同様です。

“`javascript
export default {
data() {
return {
products: [
{ id: 1, name: ‘ノートPC’, price: 120000 },
{ id: 2, name: ‘スマートフォン’, price: 80000 },
{ id: 3, name: ‘タブレット’, price: 50000 }
],
sortKey: ‘price’, // ソート基準 (‘price’ or ‘name’)
sortOrder: ‘asc’ // ソート順 (‘asc’ or ‘desc’)
}
},
computed: {
// products, sortKey, sortOrder に依存する sortedProducts プロパティ
sortedProducts: function() {
console.log(‘sortedProducts computed が実行されました’);
// 元の配列を破壊しないようにコピーを作成してからソート
const sorted = […this.products];
sorted.sort((a, b) => {
const keyA = a[this.sortKey];
const keyB = b[this.sortKey];

    let comparison = 0;
    if (keyA > keyB) {
      comparison = 1;
    } else if (keyA < keyB) {
      comparison = -1;
    }

    // ソート順によって結果を反転
    return this.sortOrder === 'desc' ? (comparison * -1) : comparison;
  });
  return sorted;
}

},
methods: {
// ソート基準と順序を切り替えるメソッド
sortBy(key) {
// クリックされたキーが現在のソートキーと同じなら、順序を反転
this.sortOrder = this.sortKey === key ? (this.sortOrder === ‘asc’ ? ‘desc’ : ‘asc’) : ‘asc’;
// ソートキーを設定
this.sortKey = key;
}
}
}
“`

“`html

商品リスト


  • {{ product.name }} – {{ product.price }}円

“`

この例では、sortedProductsというcomputedプロパティが、元のproducts配列、そしてソートの基準となるsortKeysortOrderに依存しています。ボタンをクリックしてsortKeysortOrderを変更すると、sortedProductsが再計算され、商品の表示順が自動的に更新されます。

4.4 条件付き表示やスタイル適用

テンプレート内でv-if:class, :styleなどを使って条件付きで何かを適用したい場合、その条件式が複雑になることがあります。このような場合にも、computedプロパティを使って条件を計算し、テンプレートをシンプルに保つことができます。

例:在庫状況に応じたボタンの有効/無効化とスタイル変更

javascript
export default {
data() {
return {
stock: 0 // 在庫数
}
},
computed: {
// 在庫が0かどうかを判定する computed プロパティ
isOutOfStock: function() {
return this.stock <= 0;
},
// ボタンのスタイルクラスを決定する computed プロパティ
buttonClasses: function() {
return {
'btn': true, // 基本クラス
'btn-danger': this.isOutOfStock, // 在庫切れなら赤色にする
'btn-success': !this.isOutOfStock // 在庫があれば緑色にする
};
}
},
methods: {
addToCart() {
// カートに追加するロジック (在庫がある場合のみ実行される想定)
if (!this.isOutOfStock) {
alert('商品をカートに追加しました!');
this.stock--; // 例として在庫を減らす
}
}
}
}

“`html

在庫数: {{ stock }}


現在、在庫切れです。

“`

css
/* スタイルの例 */
.btn {
padding: 10px 20px;
border: none;
cursor: pointer;
margin-top: 10px;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-success {
background-color: #28a745;
color: white;
}

この例では、isOutOfStockというcomputedプロパティで在庫切れかどうかを判定し、その結果をv-if:disabled属性にバインドしています。また、buttonClassesというcomputedプロパティで適用するCSSクラスを計算し、:class属性にバインドしています。stockの値を変更すると、ボタンの状態とスタイル、そして在庫切れメッセージの表示が自動的に切り替わります。

テンプレート内の:disabled="stock <= 0":class="{ 'btn-danger': stock <= 0, 'btn-success': stock > 0 }"のように直接式を書くことも可能ですが、isOutOfStockという名前のcomputedプロパティを使うことで、テンプレートの意図がより明確になります。

4.5 ゲッターとセッター (Getter and Setter)

通常、computedプロパティは値を計算して返すだけの「ゲッター」関数として定義します。これは、そのcomputedプロパティが依存データから「派生した」値であることを示しており、通常はその値を直接変更すべきではありません。変更が必要な場合は、依存元であるdataプロパティを変更します。

しかし、例外的に、computedプロパティに対して値を設定(代入)したいというユースケースが存在します。このような場合のために、computedプロパティには「セッター」関数を定義することも可能です。

computedプロパティをゲッターとセッターの両方を持つプロパティとして定義する場合、関数ではなくオブジェクト形式で定義します。

javascript
export default {
data() {
return {
firstName: '太郎',
lastName: '山田'
}
},
computed: {
// fullName という名前の computed プロパティをオブジェクト形式で定義
fullName: {
// ゲッター関数: 値を取得するときに呼ばれる
get: function() {
console.log('fullName getter が実行されました');
return this.lastName + ' ' + this.firstName;
},
// セッター関数: 値を設定するときに呼ばれる
set: function(newValue) {
console.log('fullName setter が実行されました', newValue);
// 受け取った新しい値 (newValue) を分割して、依存元の data プロパティを更新する
const names = newValue.split(' ');
this.lastName = names[0] || ''; // 姓
this.firstName = names[1] || ''; // 名
// 注意: セッターの中で computed プロパティ自身 (this.fullName) を再度変更しようとすると無限ループになる可能性があるので避ける
}
}
}
}

テンプレートでは、ゲッターのみの場合と同じようにプロパティとして参照します。

“`html

氏名: {{ fullName }}


“`

この例では、v-modelを使って<input>要素とfullNameというcomputedプロパティを双方向バインドしています。

  1. 初期表示時: fullNameのゲッターが実行され、firstNamelastNameから計算された値が<input>value<p>タグに表示されます。
  2. ユーザーが<input>の値を変更したとき: v-modelによって変更された新しい値がfullNameセッターに渡されます。
  3. セッター関数内で、受け取った値を分割し、依存元であるfirstNamelastNameというdataプロパティを更新します。
  4. firstNameまたはlastNameが変更されたため、fullNameのゲッターが再実行され、新しい計算結果が<p>タグに反映されます。

このように、ゲッターとセッターを持つcomputedプロパティは、フォーム入力など、複数の入力データを結合した値を表示しつつ、その表示された値を編集することで元の入力データ群を更新したいという、双方向バインディングのようなシナリオで非常に有用です。

ただし、セッターを定義することは、computedプロパティの本来の性質である「依存データから派生した値を読み込む」という性質からは少し外れるため、必要な場合に限定して使用するのが良いでしょう。ゲッターのみで十分なケースがほとんどです。

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

computedプロパティはその強力さゆえに、誤った使い方をすると予期せぬ挙動を引き起こしたり、パフォーマンスの問題につながったりする可能性があります。ここでは、computedを使う上での注意点と推奨される使い方を見ていきます。

5.1 computedプロパティ内での副作用 (Side Effects) の禁止

これはcomputedプロパティを使う上で最も重要なルールの一つです。

computedプロパティのゲッター関数内では、コンポーネントのデータを直接変更したり、非同期処理を行ったり、DOMを直接操作したりといった、副作用を伴う処理を行うべきではありません。

なぜか?

  • 実行タイミングが予測しにくい: computedはVueのリアクティブシステムによって、依存データが変更されたときに「必要に応じて」実行されます。その実行タイミングはVueの内部的な最適化やレンダリングサイクルに依存するため、メソッドのように「ボタンをクリックしたとき」「データが更新された直後」といった明確なタイミングで実行されるわけではありません。予期しないタイミングでデータが変更されたり、DOM操作が行われたりすると、アプリケーションの状態が不安定になる可能性があります。
  • キャッシュの仕組みと矛盾: computedの目的は純粋な計算結果をキャッシュすることです。計算以外の副作用が含まれていると、キャッシュされた値が使われたときに副作用が実行されなかったり、逆に再計算されたときに意図せず副作用が再実行されたりして、期待通りの動作になりません。
  • デバッグの困難化: データの変更やUIの更新が、どのcomputedプロパティのどの副作用によって引き起こされたのかを追跡するのが非常に難しくなります。

computedプロパティは、あくまで既存のデータから新しいデータを計算して返すという「純粋な関数」であるべきです。データの変更や非同期処理、DOM操作などの副作用は、methods、ライフサイクルフック(mounted, updatedなど)、またはVue 3のComposition APIにおけるwatchや非同期関数の中で行うべきです。

悪い例:

javascript
computed: {
// BAD: computedの中でデータを変更している
calculatedValue: function() {
this.someData = this.otherData * 2; // 副作用!データの変更
return this.someData;
}
}

javascript
computed: {
// BAD: computedの中で非同期処理をトリガーしている
userData: function() {
// 副作用!非同期処理の開始
fetch(`/api/users/${this.userId}`).then(response => response.json()).then(data => {
this.user = data; // 副作用!データの変更
});
return this.user; // 非同期処理の結果が返る前に計算結果が返る可能性もある
}
}

これらの副作用を伴う処理は、methodscreated/mountedなどのライフサイクルフックで行うべきです。

5.2 計算コストが高い場合は?

computedプロパティはキャッシングされるため、計算コストがある程度高くても、依存データが頻繁に変更されない限りパフォーマンス上の問題になりにくいというメリットがあります。しかし、依存データが非常に頻繁に更新される場合や、計算自体が極めて重い(数秒かかるような)場合、それでも再計算のオーバーヘッドが無視できなくなる可能性はあります。

ほとんどのアプリケーションでは、通常の配列操作(filter, map, reduce, sortなど)程度の計算であれば、computedで十分パフォーマンスは出ます。もしパフォーマンスがボトルネックになっていることが明確になった場合は、Vue Devtoolsのプロファイラなどを使ってどこに時間がかかっているかを特定し、以下の対策を検討できます。

  • 計算ロジック自体の最適化。
  • 計算結果を非同期で取得し、別のデータプロパティに格納する(この場合、computedではなくwatchを使うか、非同期メソッドを使う)。
  • 大きなリストを扱う場合は、仮想リスト(Virtual Scroll)などの手法を検討する。

しかし、これらの最適化は一般的なケースでは不要であり、まずはcomputedを使ってシンプルに記述することから始めるのが良いでしょう。

5.3 getter関数は同期的に値を返すべき

computedプロパティのゲッター関数は、必ず同期的に計算結果の値を返さなければなりません。非同期処理(setTimeout, Promise, async/awaitなど)の中で計算を行い、その結果を非同期的に返すことはできません。

もし非同期処理の結果を元に表示する値を計算したい場合は、以下のいずれかの方法を取ります。

  • 非同期処理の結果をdataプロパティに格納し、そのdataプロパティを元にcomputedプロパティを定義する。
  • Vue 3のComposition APIのasync computedライブラリなどを検討する(Options API標準には非同期computedはありません)。
  • watchプロパティを使って、非同期処理のトリガーとなるデータが変更されたときに非同期処理を実行し、その結果を別のデータプロパティに格納する。

例: watch を使って非同期処理の結果を表示する

javascript
export default {
data() {
return {
userId: 1,
user: null, // 非同期で取得したユーザー情報を格納するデータプロパティ
loading: false
}
},
watch: {
// userId が変更されたら、非同期処理を実行
userId: {
immediate: true, // コンポーネント作成時にも実行
handler(newUserId) {
this.loading = true;
// 非同期処理
fetch(`/api/users/${newUserId}`)
.then(response => response.json())
.then(data => {
this.user = data; // データを更新
})
.catch(error => {
console.error('ユーザーデータの取得に失敗しました:', error);
this.user = null; // エラー時はデータをクリアなど
})
.finally(() => {
this.loading = false;
});
}
}
},
computed: {
// user データに基づいて表示文字列を計算
displayUser: function() {
if (this.loading) {
return '読み込み中...';
} else if (this.user) {
return `${this.user.name} (${this.user.email})`;
} else {
return 'ユーザー情報なし';
}
}
}
}

この例では、userId の変更を watch で監視し、変更があったときに非同期でユーザーデータを取得しています。取得したデータは user という data プロパティに格納されます。そして computed プロパティである displayUser は、この user データに基づいて表示する文字列を計算しています。このように、非同期処理は watch やメソッドで行い、その結果をリアクティブなデータとして保持し、computedはそのデータを元に表示用の値を計算するという役割分担が適切です。

5.4 computedプロパティ名は分かりやすく命名する

computedプロパティは、その計算結果が何を表しているのかを明確に伝える名前をつけるべきです。テンプレートで参照されたときに、どのような値がそこに入っているのかが容易に推測できるようにすることで、コードの可読性が向上します。

良い例:

  • fullName
  • totalAmount
  • filteredItems
  • isLoggedIn
  • canSubmitForm

避けるべき例:

  • result
  • dataProcessed
  • calculate (計算する、という動詞ではなく、計算された「値」を表す名詞や形容詞が良い)

5.5 computedプロパティ内で引数は受け取れない(基本的には)

computedプロパティは、テンプレートで{{ computedPropertyName }}のようにプロパティ参照として使用されるため、メソッドのように{{ methodName(arg) }}のように引数を渡すことはできません。

もし、引数によって計算結果を変えたい場合は、computedプロパティではなくメソッドを使用します。メソッドであれば、テンプレートから引数を渡して呼び出すことができます。

または、Vue 3のComposition APIでは、computed内に引数を渡せるような関数を返すパターン(Computed Ref with Function)も可能ですが、Options APIの標準的なcomputedプロパティの定義では引数は扱えません。

例:引数を必要とする計算はメソッドで

javascript
export default {
data() {
return {
items: [
{ id: 1, name: 'A', price: 100 },
{ id: 2, name: 'B', price: 200 },
{ id: 3, name: 'C', price: 300 }
]
}
},
methods: {
// アイテムの ID を受け取って、そのアイテムの税込み価格を計算して返すメソッド
getTaxedPrice(itemId) {
const item = this.items.find(item => item.id === itemId);
if (item) {
return (item.price * 1.10).toFixed(2);
}
return 'N/A';
}
}
}

“`html

  • {{ item.name }}: {{ item.price }}円 (税込み: {{ getTaxedPrice(item.id) }}円)

“`

この例のように、「リストの各要素に対して異なる計算を行う」といった、要素ごとに異なる引数が必要な場合は、メソッドを使うのが適切です。なぜなら、これはリスト全体に対する一つの計算結果ではなく、リストの各要素それぞれに対する個別の計算だからです。computedはリスト全体をフィルタリング・ソートして「加工されたリスト」を返す、というような使い方には適していますが、「リストの各要素を引数に何かを計算して表示する」という用途には向いていません。

ただし、もしリスト全体を処理した結果として「ある条件を満たすアイテムだけを集めたリスト」や「特定の順番に並べ替えたリスト」を得たいのであれば、それはリスト全体に依存する「計算されたリスト」となるため、computedが適切です。(先述のフィルタリング・ソートの例を参照)

6. 実践的なサンプルコード:TODOリスト(発展)

これまでに学んだcomputedプロパティの知識を組み合わせて、少し発展的なTODOリストのサンプルコードを見てみましょう。この例では、フィルタリング、未完了数の計算、そしてフィルタリング条件の切り替えにcomputedを活用します。

“`html

“`

このサンプルコードのポイントです。

  • dataには、TODOアイテムのリスト(todos)、新しいTODOの入力値(newTodoText)、フィルタリングの状態(currentFilter)など、アプリケーションの状態を保持するデータがあります。
  • methodsには、ユーザーの操作に応じてデータを変更する処理(addTodo, removeTodo, markAllDone)が定義されています。これらのメソッドはtodos配列やその中のオブジェクトのプロパティを直接変更しています。
  • computedプロパティとして、
    • filteredTodos: 現在選択されているフィルター (currentFilter) と元のtodos配列に依存して、表示すべきTODOアイテムのリストを計算しています。currentFilterが変わるか、todos配列の中身が変わるたびに再計算されます。テンプレートのv-forはこのfilteredTodosを参照しています。
    • remainingTasksCount: todos配列全体の未完了タスクの数を計算しています。todos配列の中身が変わるたびに再計算されます。
  • フィルタリングボタンは、クリック時にcurrentFilterというdataプロパティの値を変更します。この変更がfilteredTodos computed の依存関係をトリガーし、自動的にリストが再レンダリングされます。
  • 各TODOアイテムのチェックボックスはv-model="todo.done"を使って双方向バインディングされています。チェックボックスの状態が変わると、todo.doneというデータが変更されます。このdoneプロパティの変更は、todos配列の中身の変更としてVueに検知されるため、filteredTodosremainingTasksCount computed の両方が再計算されます。

このように、computedプロパティを使うことで、データの表示形式(フィルタリング、集計など)を、元のデータの変更やユーザーによるフィルター選択に応じて自動的に、かつ効率的に更新することができます。これは、Vue.jsにおけるリアクティブなUI開発の中核をなす考え方です。

7. まとめ

この記事では、Vue.jsのcomputedプロパティについて、初心者向けに基礎から応用までを徹底的に解説しました。

重要なポイントを改めてまとめましょう。

  • computedプロパティは、コンポーネントの既存のデータに基づいて計算された値を提供するものです。
  • テンプレートでは{{ computedPropertyName }}のようにプロパティとして参照します。
  • computedの最大の特徴は、キャッシング依存関係の追跡です。
    • 依存データが変更されない限り、計算結果はキャッシュされ再利用されるため、パフォーマンスに優れています。
    • 依存するデータが変更されたときにのみ、Vue.jsが自動的に再計算します。
  • methodsは特定の処理を実行する関数であり、呼び出されるたびに実行されます(キャッシングも依存追跡もありません)。イベントハンドリングやデータを変更する処理に使います。
  • データを加工して表示する場合(フィルタリング、ソート、集計、組み合わせなど)は、computedが第一の選択肢です。
  • computedプロパティは通常ゲッター関数として定義しますが、双方向バインディングなどの特殊なケースではゲッターとセッターを定義できます。
  • computedプロパティ内では副作用(データの変更、DOM操作、非同期処理など)を避けるべきです。これらはmethodswatchで行います。
  • computedプロパティは引数を取れません。引数によって計算結果を変えたい場合はメソッドを使います。
  • 分かりやすいcomputedプロパティ名を付けることで、コードの可読性が向上します。

computedプロパティを理解し、適切に使いこなすことは、Vue.jsで効率的かつ保守性の高いアプリケーションを開発する上で非常に重要です。最初はmethodsとの違いに戸惑うかもしれませんが、計算結果をキャッシュしたいか、それとも毎回処理を実行したいか、という観点で考えると使い分けがしやすくなるはずです。

この記事で学んだ知識を元に、ぜひ実際にコードを書いてcomputedプロパティの挙動を体感してみてください。Vue.jsでの開発がもっと楽しく、効率的になるはずです!

8. よくある質問 (FAQ)

Q: methodsとcomputedの最も重要な違いは何ですか?

A: 最も重要な違いは、キャッシングの有無依存関係の追跡です。

  • computed: 依存データが変更されない限り、計算結果はキャッシュされます。依存データが変更されたときにのみ自動的に再計算されます。値を計算して返す用途に使います。
  • methods: 呼び出されるたびに毎回実行されます。キャッシングも依存追跡もありません。イベントハンドリングや特定の処理を実行する用途に使います。

計算して表示する値であれば、特別な理由がない限りcomputedを使う方がパフォーマンスと可読性の面で優れています。

Q: computedはなぜキャッシングされるのですか?

A: computedプロパティは、その計算結果が「依存データから派生した値」であり、その値がテンプレートなどで頻繁に参照されることを想定しているため、効率化のためにキャッシングの仕組みが導入されています。依存データが変わっていないのに何度も同じ計算をするのは無駄だからです。Vue.jsのリアクティブシステムが依存関係を自動的に追跡し、依存データが変更されたかどうかを判断してキャッシングを制御しています。

Q: computedで非同期処理はできますか?

A: いいえ、computedプロパティのゲッター関数は同期的に値を返さなければなりません。非同期処理(fetch, setTimeout, Promiseなど)をcomputedのゲッター内で直接行うことは推奨されませんし、意図したとおりに動作しない場合が多いです。非同期処理が必要な場合は、methods内で実行し、その結果をdataプロパティに格納するか、watchを使って非同期処理をトリガーし、その結果をdataプロパティに格納するという方法を取ります。そして、computedはそのdataプロパティを元に計算を行います。

Q: computedはパフォーマンスにどう影響しますか?

A: computedプロパティは、依存データが変更されない限り再計算されないため、パフォーマンスを向上させる効果があります。特に、計算コストが高い処理をテンプレートで直接行ったり、メソッドで毎回実行したりする代わりにcomputedを使うことで、不要な再計算が減り、アプリケーション全体の応答性が向上します。ただし、依存データが極めて頻繁に変更され、かつ計算自体も非常に重いといった特殊なケースでは、それでもパフォーマンスが問題になる可能性はあります。

Q: computedプロパティ内でデータを変更できますか?

A: computedプロパティのゲッター関数内では、コンポーネントのデータ(dataや他のcomputedプロパティ)を直接変更する副作用は厳禁です。これは予期せぬ挙動やデバッグの困難化を招きます。データを変更したい場合は、methodsやイベントハンドラの中で行うべきです。

ただし、ゲッターとセッターを持つcomputedプロパティを定義した場合は、セッター関数内で依存元のdataプロパティを変更することは可能ですし、それがセッターの主な用途です。しかし、この場合もセッター内でcomputedプロパティ自身を直接更新しようとすると無限ループになる可能性があるため注意が必要です。


これで、Vue.jsのcomputedプロパティに関する詳細な解説は終わりです。この記事が、皆さんのVue.js学習の一助となれば幸いです。

コメントする

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

上部へスクロール