はい、承知いたしました。
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 // 真偽値データ
}
}
}
これらのmessage
、counter
、isLoggedIn
といったデータが変更されると、そのデータが表示されているテンプレートの部分が自動的に更新されます。
1.2 テンプレート構文 (データの表示、ディレクティブ)
Vue.jsのテンプレートは、HTMLをベースに、Vue独自の拡張構文(ディレクティブ)が追加されたものです。テンプレートを使って、data
プロパティで定義したデータを表示したり、ユーザーの操作に応じた条件分岐や繰り返し処理を行ったりします。
-
データの表示(Mustache構文
{{ }}
):
{{ dataプロパティ名 }}
の形式で、データを直接テンプレートに埋め込みます。html
<p>{{ message }}</p>
<p>現在のカウンター: {{ counter }}</p> -
ディレクティブ (v- で始まる属性):
特定の振る舞いを要素に追加する際に使用します。代表的なものには以下があります。v-bind
: HTML属性にデータをバインドする(例::href
はv-bind:href
の省略形)v-if
,v-else-if
,v-else
: 条件付きレンダリングv-for
: リストレンダリングv-on
: イベントハンドリング(例:@click
はv-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() }}
“`
これは確かに動作します。しかし、このアプローチにはいくつかの欠点があります。特に「パフォーマンス」と「可読性」の面で問題が生じることがあります。
-
パフォーマンスの問題 (Methodsは毎回実行される):
{{ getFullName() }}
のようにテンプレート内でメソッドを呼び出すと、そのコンポーネントが再レンダリングされるたびに、このメソッドが毎回実行されます。Vue.jsは、依存するデータが変更されたときにコンポーネントの一部または全体を再レンダリングしますが、この再レンダリングのたびにメソッドは無条件に呼び出されてしまうのです。もし、そのメソッドの計算コストが高かったり、テンプレート内で頻繁に呼び出されたりする場合、これはパフォーマンスの低下を招く可能性があります。 -
可読性の問題 (テンプレートが複雑になる):
簡単な計算であればテンプレート内で直接式を書くことも可能ですが、少し複雑な計算やデータ加工が必要になると、テンプレート内の式が非常に長くなり、読みにくくなってしまいます。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 }}
“`
このコードを実行し、firstName
やlastName
の値を変更してみてください。コンソールにはfullName computed が実行されました
というログが出力され、テンプレートの氏名表示が自動的に更新されることが確認できます。
一方、firstName
やlastName
を変更せずに、コンポーネントの別の部分で何か変更があり、再レンダリングが発生したとします(例えば、別のデータプロパティを変更するボタンをクリックするなど)。このとき、fullName
computed が再計算されることはありません。ログが出力されないことで、キャッシングされていることが確認できます。
これが、computed
プロパティの核となる仕組みであり、methods
との決定的な違いです。
3. computedのメリットとmethodsとの比較
computed
プロパティがなぜ有用なのか、そのメリットをmethods
との比較を通してさらに詳しく見ていきましょう。主なメリットは以下の2点です。
- キャッシング (Caching): 依存データが変更されない限り、計算結果がキャッシュされ、再利用される。
- 依存関係の追跡 (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 }}
“`
このコードを実行し、以下の操作をしてみてください。
- ページを読み込む:
fullName computed が実行されました
と表示されます。 changeMessage
ボタンをクリックする:message
が変更され、テンプレートが更新されます。しかし、fullName computed が実行されました
は表示されません。これは、fullName
が依存しているfirstName
とlastName
が変更されていないため、キャッシュされた結果が使われたからです。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 }}個
“`
この例で、以下の操作を試してください。
- ページ読み込み:
totalAmount computed が実行されました
とitemCount computed が実行されました
が表示されます。 addItem
ボタンをクリック:items
配列に新しいアイテムが追加されます。items
配列そのものが変更されたため、totalAmount
とitemCount
の両方が依存データ変更を検知し、両方とも再計算され、ログが表示されます。increaseFirstItemQuantity
ボタンをクリック:items
配列の最初の要素のquantity
プロパティが変更されます。Vue.jsは配列の中のオブジェクトのプロパティ変更も追跡します。totalAmount
はitems
内の各アイテムのquantity
に依存しているため、totalAmount
は再計算され、ログが表示されます。しかし、itemCount
はitems
配列のlength
にのみ依存しており、length
は変更されていないため、itemCount
は再計算されず、ログは表示されません。キャッシュされた値が使われます。
このように、computed
プロパティは、自身の関数内で実際に参照したデータだけを賢く追跡し、必要なときにだけ再計算を行うのです。これは、Vue.jsのリアクティブシステムの強力な機能の一つです。
3.3 コードの可読性と宣言的プログラミング
もう一つの重要なメリットは、コードの可読性と保守性の向上です。
テンプレート内で複雑なJavaScript式を書く代わりに、その計算ロジックをcomputed
プロパティとしてコンポーネントのJavaScript部分に切り出すことで、テンプレートが非常にシンプルで読みやすくなります。テンプレートは「何を表示するか」に集中でき、計算やデータ加工の詳細はコンポーネントのロジック部分にまとめられます。
“`html
在庫のあるアイテムがあります。合計金額: {{ totalAmountInStock }}円
“`
このように、複雑な計算や条件を名前付きのcomputed
プロパティにすることで、テンプレートを読む人がそのプロパティの意図をすぐに理解できるようになります。また、計算ロジックの変更が必要になった場合でも、テンプレートではなくJavaScript部分だけを修正すれば済むため、保守も容易になります。
これは、Vue.jsが推奨する「宣言的プログラミング」の考え方にも合致します。UIの見た目(テンプレート)はデータの状態に基づいて自動的に決定され、その状態をどのように加工・計算するかは、リアクティブなプロパティ(data
やcomputed
)として宣言的に定義されるべき、という考え方です。
computed
とmethods
の使い分けまとめ
これまでの説明をまとめると、computed
とmethods
は以下の基準で使い分けるのが一般的です。
特徴 | 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 }}円
“`
この例では、totalAmount
は subtotal
と taxAmount
に依存しています。また、taxAmount
は subtotal
に依存しています。そして subtotal
は price
と quantity
に依存しています。displayTotalAmount
は totalAmount
に依存しています。
price
、quantity
、またはtaxRate
のいずれかを変更すると、関連するcomputed
プロパティが連鎖的に再計算されます。しかし、例えばprice
だけを変更した場合、subtotal
、taxAmount
、totalAmount
、displayTotalAmount
は再計算されますが、それ以外の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
が再計算されます。remainingTasksCount
はtodos
配列の中身(特に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
配列、そしてソートの基準となるsortKey
とsortOrder
に依存しています。ボタンをクリックしてsortKey
やsortOrder
を変更すると、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
プロパティを双方向バインドしています。
- 初期表示時:
fullName
のゲッターが実行され、firstName
とlastName
から計算された値が<input>
のvalue
と<p>
タグに表示されます。 - ユーザーが
<input>
の値を変更したとき:v-model
によって変更された新しい値がfullName
のセッターに渡されます。 - セッター関数内で、受け取った値を分割し、依存元である
firstName
とlastName
というdata
プロパティを更新します。 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; // 非同期処理の結果が返る前に計算結果が返る可能性もある
}
}
これらの副作用を伴う処理は、methods
やcreated
/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
TODO リスト
-
{{ todo.text }}
未完了タスク数: {{ remainingTasksCount }}
“`
このサンプルコードのポイントです。
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に検知されるため、filteredTodos
とremainingTasksCount
computed の両方が再計算されます。
このように、computed
プロパティを使うことで、データの表示形式(フィルタリング、集計など)を、元のデータの変更やユーザーによるフィルター選択に応じて自動的に、かつ効率的に更新することができます。これは、Vue.jsにおけるリアクティブなUI開発の中核をなす考え方です。
7. まとめ
この記事では、Vue.jsのcomputed
プロパティについて、初心者向けに基礎から応用までを徹底的に解説しました。
重要なポイントを改めてまとめましょう。
computed
プロパティは、コンポーネントの既存のデータに基づいて計算された値を提供するものです。- テンプレートでは
{{ computedPropertyName }}
のようにプロパティとして参照します。 computed
の最大の特徴は、キャッシングと依存関係の追跡です。- 依存データが変更されない限り、計算結果はキャッシュされ再利用されるため、パフォーマンスに優れています。
- 依存するデータが変更されたときにのみ、Vue.jsが自動的に再計算します。
methods
は特定の処理を実行する関数であり、呼び出されるたびに実行されます(キャッシングも依存追跡もありません)。イベントハンドリングやデータを変更する処理に使います。- データを加工して表示する場合(フィルタリング、ソート、集計、組み合わせなど)は、
computed
が第一の選択肢です。 computed
プロパティは通常ゲッター関数として定義しますが、双方向バインディングなどの特殊なケースではゲッターとセッターを定義できます。computed
プロパティ内では副作用(データの変更、DOM操作、非同期処理など)を避けるべきです。これらはmethods
やwatch
で行います。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学習の一助となれば幸いです。