【Vue.js】filterの使い方を徹底解説!データ整形に必須だった機能(そしてその変遷)
Vue.jsは、直感的で柔軟なUI構築を可能にするプログレッシブフレームワークです。データとDOMを連携させる際に、取得した生のデータをそのまま表示するのではなく、特定の形式に整形したり、加工したりしたい場面は多々あります。例えば、APIから取得したタイムスタンプを「YYYY年MM月DD日」形式に変換したり、数値を通貨形式(例: 1,234円)で表示したり、長いテキストを省略して末尾に「…」を付けたりする場合などです。
このような「表示のためのデータ整形」を効率的に行うための機能として、Vue.jsにはかつてフィルター(Filters)という機能が提供されていました。この記事では、Vue.jsのフィルター機能がどのようなもので、どのように使い、どのようなメリット・デメリットがあったのかを徹底的に解説します。
しかし、重要な点として、Vue.js 3.x 以降、フィルター機能は非推奨となり、公式には廃止されています。 したがって、この記事では、フィルターの解説に加えて、なぜ非推奨となったのか、そしてVue 3以降で推奨される代替手段(計算プロパティやメソッドなど)についても詳しく掘り下げます。
Vue.jsの学習者、特にVue 2からVue 3への移行を考えている方、あるいは単に表示データの整形方法についてベストプラクティスを知りたい方にとって、この記事が網羅的で役立つ情報源となることを目指します。
この記事で学べること
- Vue.jsフィルター機能の基本的な概念と構文
- ローカルフィルターとグローバルフィルターの定義方法と使い方
- フィルターの応用的な使い方(引数、チェイン、外部ライブラリ連携)
- フィルター機能が非推奨となった理由
- Vue 3以降で推奨されるフィルターの代替手段(計算プロパティ、メソッドなど)とその使い方
- フィルターから代替手段への移行方法
さあ、Vue.jsにおけるデータ整形の歴史と現代的な手法を深く理解していきましょう。
1. Vue.js filterの基本
まず、Vue.jsのフィルター機能がどのようなものだったのかを理解することから始めましょう。
1.1. filterとは何か?(概念)
Vue.jsのフィルターは、主にテンプレート内でデータの表示形式を整形するために使われるシンプルな関数です。パイプ |
記号を使って、表示したいデータにフィルターを適用します。これは、Unix/Linuxのシェルコマンドにおけるパイプラインの考え方に似ています。
“`html
{{ rawData }}
{{ rawData | filterName }}
“`
フィルターは入力値を受け取り、加工した新しい値を返します。元のデータ自体を変更するわけではなく、あくまで表示する際の形式のみを変換します。これは非常に重要な点です。
1.2. 構文
フィルターは主にテンプレート内の二箇所で使用できます。
- Mustache記法({{ }})内:
html
{{ data | filterName }} v-bind
ディレクティブ内:
html
<img v-bind:alt="altText | filterName">
<!-- 短縮記法 -->
<img :alt="altText | filterName">
フィルターは、パイプ |
の後にフィルター名を記述します。
1.3. 単一フィルター
最も基本的な使い方です。一つのデータに対して一つのフィルターを適用します。
“`html
元のデータ: {{ originalDate }}
整形後: {{ originalDate | formatDate }}
“`
この例では、originalDate
というデータに対して、formatDate
という名前のフィルターを適用しています。formatDate
フィルターは、受け取った日付データを特定の文字列形式に変換する役割を持ちます。
1.4. 複数フィルター(チェイン)
複数のフィルターを連続して適用することも可能です。パイプ |
で区切ってフィルター名を記述します。
“`html
整形後: {{ longText | truncate | uppercase }}
“`
この例では、longText
というデータにまず truncate
フィルターを適用し、その出力結果に対してさらに uppercase
フィルターを適用しています。フィルターは左から右へ順番に実行されます。
1.5. 引数を持つフィルター
フィルターには引数を渡すこともできます。フィルター名の後に括弧 ()
を使い、引数をカンマ ,
で区切って記述します。
“`html
整形後: {{ price | formatCurrency(‘円’, 0) }}
“`
この例では、price
というデータに formatCurrency
フィルターを適用しています。formatCurrency
フィルターには引数として '円'
と 0
が渡されています。通常、フィルター関数は最初の引数としてフィルターを適用される値(ここでは price
)を受け取り、それ以降の引数としてテンプレートで渡された引数(ここでは '円'
と 0
)を受け取ります。
テンプレート構文まとめ:
“`html
{{ value | filterName }}
{{ value | filterA | filterB }}
{{ value | filterName(arg1, arg2) }}
{{ value | filterA(arg1) | filterB(arg2, arg3) }}
“`
2. filterの定義方法
フィルターは、コンポーネント内でローカルに定義する方法と、アプリケーション全体でグローバルに定義する方法がありました。
2.1. ローカルフィルター(コンポーネント内)
特定のコンポーネント内でのみ使用したいフィルターは、そのコンポーネントのオプション内で定義します。
定義方法:
コンポーネントのオプションオブジェクトに filters
というプロパティを追加し、その中にフィルター名と対応する関数をオブジェクトとして定義します。
“`javascript
export default {
data() {
return {
message: ‘Hello Vue Filter’,
longText: ‘This is a very long text that needs to be truncated.’,
price: 12345,
status: ‘pending’,
timestamp: 1678886400000 // 例: Unixタイムスタンプ (ミリ秒)
};
},
filters: {
// フィルター名: 関数本体
uppercase(value) {
if (!value) return ”;
return value.toString().toUpperCase();
},
truncate(value, length = 10, suffix = ‘…’) {
if (!value) return ”;
value = value.toString();
if (value.length <= length) {
return value;
}
return value.substring(0, length) + suffix;
},
formatCurrency(value, currency = ‘¥’, decimalPlaces = 0) {
if (typeof value !== ‘number’) return value;
const formatter = new Intl.NumberFormat(‘ja-JP’, {
style: ‘currency’,
currency: ‘JPY’, // 日本円を前提
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
});
// Intl.NumberFormatは通貨記号を自動で含めるため、currency引数は飾りとして使う
// より柔軟な通貨記号指定が必要な場合は別の方法を検討
// 例として、単に数値にカンマを付け、指定した通貨記号を後置する場合:
const parts = value.toFixed(decimalPlaces).split(‘.’);
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ‘,’);
return parts.join(‘.’) + currency;
// 上記は例。実際の Intl.NumberFormat を使うと地域設定に沿った形式になる
// return formatter.format(value); // こちらが一般的
},
formatDate(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
return `${year}/${month}/${day}`;
},
statusText(value) {
if (!value) return '';
const statusMap = {
'pending': '保留中',
'processing': '処理中',
'completed': '完了',
'failed': '失敗',
};
return statusMap[value] || value; // マップにない場合は元の値を返す
}
}
};
“`
使い方:
テンプレート内でパイプ |
を使って、定義したフィルター名を呼び出します。
“`html
ローカルフィルターの例
元データ: {{ message }}
uppercase 適用: {{ message | uppercase }}
元データ: “{{ longText }}”
truncate (デフォルト) 適用: “{{ longText | truncate }}”
truncate (長さ 20, 接尾辞 “…”) 適用: “{{ longText | truncate(20, ‘…’) }}”
truncate (長さ 5, 接尾辞 “…”) 適用: “{{ longText | truncate(5) }}”
元データ: {{ price }}
formatCurrency (円) 適用: {{ price | formatCurrency(‘円’, 0) }}
formatCurrency ($, 小数点2桁) 適用: {{ 9876.54321 | formatCurrency(‘$’, 2) }}
元データ: {{ timestamp }}
formatDate 適用: {{ timestamp | formatDate }}
元データ: {{ status }}
statusText 適用: {{ status | statusText }}
statusText (未知の値) 適用: {{ ‘unknown_status’ | statusText }}
複数のフィルターをチェイン
{{ longText | truncate(30) | uppercase }}
{{ price | formatCurrency(‘ドル’, 2) | uppercase }}
“`
ローカルフィルターのメリット:
- コンポーネント内で完結するため、名前の衝突を気にせずに自由に定義できる。
- 特定のコンポーネントに特化した整形処理に使いやすい。
ローカルフィルターのデメリット:
- 複数のコンポーネントで同じフィルターを使いたい場合、それぞれのコンポーネントで同じ定義を繰り返す必要がある(DRY違反になりがち)。
- グローバルフィルターに比べて管理が分散する。
2.2. グローバルフィルター(アプリケーション全体)
アプリケーション全体で広く使われるフィルターは、グローバルに定義すると便利です。
定義方法:
Vueインスタンスを生成する前(通常は main.js
や app.js
のようなエントリーファイル)で、Vue.filter()
メソッドを使って定義します。
“`javascript
// main.js (Vue 2 の場合)
import Vue from ‘vue’;
import App from ‘./App.vue’;
// グローバルフィルターの定義
Vue.filter(‘capitalize’, function(value) {
if (!value) return ”;
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});
Vue.filter(‘reverse’, function(value) {
if (!value) return ”;
return value.toString().split(”).reverse().join(”);
});
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount(‘#app’);
“`
Vue.filter()
メソッドは、第一引数にフィルター名(文字列)、第二引数にフィルター関数を指定します。
使い方:
グローバルフィルターは、定義後であればどのコンポーネントのテンプレート内でも、ローカルフィルターと同じ構文で利用できます。
“`html
グローバルフィルターの例
元データ: {{ message }}
capitalize 適用: {{ message | capitalize }}
reverse 適用: {{ message | reverse }}
チェイン適用: {{ message | capitalize | reverse }}
{{ longText | truncate(15) | capitalize }}
```
グローバルフィルターのメリット:
- 一度定義すれば、アプリケーション全体で再利用できる(DRY原則に則る)。
- 共通の整形処理を集中管理できる。
グローバルフィルターのデメリット:
- 名前の衝突に注意する必要がある。
- アプリケーションのどこからでもアクセスできるため、不用意にたくさんのグローバルフィルターを作りすぎると管理が煩雑になる可能性がある。
2.3. 実践的なコード例(日付フォーマットと通貨フォーマット)
ローカルフィルターとグローバルフィルターを組み合わせた実践的な例を見てみましょう。
main.js
(グローバルフィルター定義)
```javascript
import Vue from 'vue';
import App from './App.vue';
// グローバルフィルター: 数値にカンマ区切りを追加
Vue.filter('comma', function(value) {
if (typeof value !== 'number') return value;
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
});
// グローバルフィルター: 日付オブジェクトをISO 8601形式に変換 (例)
Vue.filter('toISOString', function(value) {
if (!(value instanceof Date)) {
// Dateオブジェクトでない場合はそのまま返すか、エラー処理
return value;
}
return value.toISOString();
});
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
```
SomeComponent.vue
(ローカルフィルターとグローバルフィルターの使用)
```html
フィルター実践例
売上高: {{ salesAmount | comma }} 円
作成日時: {{ createdAt | formatDatetime('YYYY/MM/DD HH:mm:ss') }}
価格(カンマ & 通貨記号): {{ price | comma | addYenSuffix }}
ISO形式の日付: {{ currentDate | toISOString }}
```
この例では、汎用的な「カンマ区切り」や「ISO 8601形式変換」をグローバルフィルターとして定義し、特定のコンポーネントでのみ使う「詳細な日時フォーマット」や「円マーク追加」をローカルフィルターとして定義しています。このように、フィルターのスコープを適切に使い分けることで、コードの再利用性と管理性が向上します。
3. filterの応用
フィルターは単なる簡単な整形だけでなく、より複雑な処理や外部ライブラリとの連携にも利用できました。
3.1. 引数を使った複雑なデータ整形
前のセクションで引数の使い方を見ましたが、引数を使うことでフィルターの柔軟性が大幅に向上します。
例: 文字列切り詰め (truncate
) のカスタマイズ
切り詰める長さや、末尾に付ける文字列を引数で指定できるようにします。
javascript
// filter定義 (ローカル or グローバル)
filters: {
truncate(value, length = 10, suffix = '...') {
if (!value) return '';
value = value.toString();
if (value.length <= length) {
return value;
}
return value.substring(0, length) + suffix;
}
}
テンプレートでの使用:
```html
{{ longText | truncate(20) }}
{{ anotherText | truncate(15, '***') }}
```
このように、引数によってフィルターの挙動を動的に変更できます。
3.2. 複数のfilterをチェインして使う
複数のフィルターをパイプで繋げることで、一連の整形処理を行うことができます。
実行順序:
フィルターは左から右へ順に実行されます。
html
{{ value | filterA(arg1) | filterB(arg2, arg3) }}
value
がfilterA
の最初の引数として渡され、arg1
が2番目の引数として渡されてfilterA
が実行されます。filterA
の戻り値が、filterB
の最初の引数として渡され、arg2
,arg3
がそれ以降の引数として渡されてfilterB
が実行されます。filterB
の戻り値が最終的な表示結果となります。
例: 数値を通貨形式にし、さらに大文字に変換(あまり実用的ではないが、チェインの例として)
```html
{{ price | formatCurrency('$', 2) | uppercase }}
```
price
と'$', 2
がformatCurrency
に渡されます。formatCurrency
は例えば"12,345.67$"
という文字列を返します。"12,345.67$"
という文字列がuppercase
の最初の引数として渡されます。uppercase
はその文字列を"12,345.67$"
(この例では既に大文字になりうる文字がないため変化なし)として返します。
重要なのは、次のフィルターの入力は、前のフィルターの出力であるという点です。各フィルター関数は、自身の入力として渡されるデータの型を考慮して実装する必要があります。
3.3. filter内で外部ライブラリを使う
複雑な整形処理(特に日付や数値)では、moment.js、date-fns、numeral.jsなどの有名な外部ライブラリを使うことが一般的です。フィルター関数の中でこれらのライブラリを呼び出すことで、強力な整形機能を活用できます。
例: moment.jsを使った日付フォーマット
```javascript
// npm install moment
import moment from 'moment';
// filter定義 (ローカル or グローバル)
filters: {
momentFormat(timestamp, formatString = 'YYYY/MM/DD') {
if (!timestamp) return '';
// momentオブジェクトを作成し、formatメソッドを呼び出す
// timestampはミリ秒、秒、Dateオブジェクトなどmomentが解釈できる形式である必要あり
return moment(timestamp).format(formatString);
}
}
```
テンプレートでの使用:
```html
イベント日時: {{ data.eventTimestamp | momentFormat('YYYY年MM月DD日 HH時mm分') }}
簡潔な日付: {{ data.eventTimestamp | momentFormat('L') }}
```
このように、フィルター関数は普通のJavaScript関数なので、中で必要な処理(外部ライブラリの呼び出しを含む)は何でも記述できます。
3.4. filterを使った条件付き表示(簡単なもの)
フィルターは主に値の整形に使われますが、簡単な条件に基づいて異なる文字列を返すことで、「条件付き表示」のようなことも可能です。
例: ステータスコードを日本語の文字列に変換
javascript
filters: {
statusText(value) {
if (!value) return '不明';
const statusMap = {
'pending': '保留中',
'processing': '処理中',
'completed': '完了',
'failed': '失敗',
'cancelled': 'キャンセル済',
};
return statusMap[value] || `不明なステータス (${value})`; // マップにない場合は補足情報を付加
}
}
テンプレートでの使用:
```html
注文ステータス: {{ order.status | statusText }}
```
これはシンプルなケースでは便利ですが、表示する内容がHTML要素を含んだり、より複雑な条件分岐が必要な場合は、フィルターよりも計算プロパティやメソッド、あるいは v-if
/v-else
を使う方が適切です。フィルターはあくまでテキストベースの値整形に適しています。
4. filterの内部動作
フィルターはテンプレート構文の一部として特殊な扱いを受けますが、その内部では普通のJavaScript関数として実行されます。しかし、いくつかの重要な特性があります。
4.1. filterはコンポーネントインスタンスとは独立して実行される
フィルター関数は、定義されたコンポーネントのインスタンス(this
)にアクセスできません。これは、フィルターがコンポーネントの状態に依存しない、純粋な整形関数であるべきだという設計思想に基づいています。
javascript
filters: {
myFilter(value) {
// this にはアクセスできません! Undefined になります。
// console.log(this.someData); // エラーまたは undefined
return value;
}
}
もしコンポーネントのデータやプロパティに依存する整形処理が必要な場合は、フィルターは適していません。後述する計算プロパティを使うべきです。
4.2. リアクティブではない(Vue 2の場合)
これはVue 2のフィルターにおける大きな制限の一つでした。フィルターの入力値(パイプの左側の値)が変更された場合、フィルターは再実行され、表示が更新されます。しかし、フィルター関数内で参照している、入力値以外のデータが変更されても、フィルターは自動的に再評価されません。
例えば、以下のようなフィルターを考えます。
javascript
export default {
data() {
return {
items: [ /* ... */ ],
filterKeyword: '', // ユーザーが入力する検索キーワード
};
},
filters: {
// 注意:これはフィルターの本来の使い方から外れています
// フィルターは通常、リスト全体のフィルタリングには使いません
// しかし、リアクティビティの制限を示す例として考えます
filterItems(itemsArray) {
console.log("filterItems 実行");
// 問題点:ここで this.filterKeyword を参照しても、
// filterKeyword が変更されてもこのフィルターは再実行されない!
const keyword = this.filterKeyword ? this.filterKeyword.toLowerCase() : '';
if (!keyword) {
return itemsArray;
}
return itemsArray.filter(item =>
item.name.toLowerCase().includes(keyword)
);
}
},
// ...
}
そしてテンプレートで {{ items | filterItems }}
のように使用した場合:
items
配列自体が変わればfilterItems
は再実行されます。- しかし、
filterKeyword
というデータがユーザー入力などによって変更されても、filterItems
は再実行されません。したがって、表示されるリストは更新されません。
このような「入力値以外のリアクティブデータに依存する処理」には、Vue 2のフィルターは使えませんでした。リストのフィルタリングやソートのような処理は、当時から計算プロパティを使うのが一般的でした。
4.3. パフォーマンスに関する考慮事項
フィルターはシンプルで便利な機能ですが、Vue 2では特に、いくつかの点でパフォーマンスに注意が必要でした。
- キャッシュされない: フィルターはテンプレート内で使用されるたびに実行されます。もし同じ値に対して同じフィルターを複数回適用した場合、その都度フィルター関数が実行されます。計算プロパティは依存関係が変更されない限りキャッシュされるのとは対照的です。
- 複雑な処理: フィルター関数内で時間のかかる複雑な計算やDOM操作(VueのフィルターはDOM操作をすべきではありませんが)を行うと、テンプレートの再レンダリング時にパフォーマンスの問題を引き起こす可能性があります。
- リスト内での使用:
v-for
ディレクティブを使って多数のリストアイテムを表示する際に、各アイテムにフィルターを適用すると、リストが更新されるたびに多くのフィルター関数が実行されることになり、パフォーマンスが低下する可能性がありました。
これらのパフォーマンス上の特性も、後述するフィルターの非推奨化の理由の一つとなりました。
5. なぜfilterは非推奨になったのか? (Vue 3)
さて、Vue 2で便利に使われていたフィルター機能は、Vue 3で非推奨となり、公式ドキュメントから削除されました。なぜこのような変更が行われたのでしょうか?
5.1. 非推奨の理由(公式ドキュメントから)
Vue.jsの公式ドキュメントやVue 3の移行ガイドでは、フィルター機能が非推奨となった理由として主に以下の点が挙げられています。
-
テンプレート構文の過負荷(可読性の低下):
パイプ構文{{ data | filterA | filterB(arg) }}
はシンプルに見えますが、複数のフィルターをチェインしたり、引数を複雑に渡したりすると、テンプレートの可読性が低下するという意見がありました。特に、Mustache構文の中が長くなると、何を表示しようとしているのかが直感的に分かりにくくなります。
また、JavaScriptの構文ではない独自の構文を追加することは、フレームワークの学習コストを上げる要因にもなりえます。 -
Vue 2での実装上の制限:
前述のように、Vue 2のフィルターはコンポーネントインスタンスのthis
にアクセスできなかったり、入力値以外のリアクティブデータに依存する更新が難しかったりといった制限がありました。これらの制限から、フィルターだけで完結できる整形処理は限られており、多くの場合は計算プロパティやメソッドと併用する必要がありました。 -
既存のJavaScriptの機能で十分代替可能:
フィルターが実現していた「表示のためのデータ整形」は、JavaScriptの標準的な機能(関数呼び出し、メソッド)とVueの既存機能(計算プロパティ、メソッド)を組み合わせることで、同等以上の機能と柔軟性、そしてより優れたリアクティビティとパフォーマンスで実現できるという結論に至りました。
特別なフィルター構文を追加するよりも、Vueの提供する他の機能(computed, methods)や、単なるJavaScript関数を使った方が、より一貫性があり、理解しやすいコードになるという考え方です。
これらの理由から、Vue CoreチームはVue 3においてフィルター機能を廃止するという決断を下しました。
5.2. Vue 3での廃止について
Vue 3では、テンプレートコンパイラがパイプ |
記号をフィルターとして特別扱いしなくなりました。したがって、Vue 3のテンプレートで {{ value | filterName }}
と記述しても、それはフィルターとして機能しません。ビルド時に警告が出るか、実行時にエラーとなる可能性が高いです。
もしVue 3で過去のフィルター構文を含むコードを実行しようとすると、期待通りに動作しないため、Vue 2からVue 3へ移行する際には、既存のフィルター機能を代替手段に置き換える作業が必須となります。
6. filterの代替手段 (Vue 3推移後のベストプラクティス)
Vue 3以降ではフィルターが廃止されたため、表示データの整形には主に以下の代替手段が推奨されます。これらはVue 2時代からフィルターと並行して使われていた方法でもあり、Vue 3ではこれらの利用が統一されました。
- 計算プロパティ (Computed Properties)
- メソッド (Methods)
- JavaScriptによる直接的なデータ変換
それぞれの使い方とフィルターとの比較を見ていきましょう。
6.1. 計算プロパティ (Computed Properties)
計算プロパティは、コンポーネントのデータやプロパティ、あるいは他の計算プロパティに基づいて算出される値を定義するのに使われます。その最も重要な特徴は、依存するリアクティブデータが変更された場合にのみ再計算され、それ以外の場合はキャッシュされた値が返されるという点です。
使い方、定義方法:
コンポーネントのオプションオブジェクトに computed
プロパティを追加し、その中に算出プロパティ名と、値を返すための関数を定義します。
javascript
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
items: [
{ name: 'Apple', price: 100 },
{ name: 'Banana', price: 150 },
{ name: 'Cherry', price: 200 },
],
searchQuery: '', // ユーザー入力
rawHtml: '<span style="color: red;">危険なHTML</span>', // 表示したいHTML
timestamp: 1678886400000,
price: 1234567,
};
},
computed: {
// フィルターの代替例1: 名前の結合
// filter: {{ firstName | fullName(lastName) }} のようなイメージ
fullName() {
console.log("fullName computed property re-calculated");
// this にアクセス可能!
return `${this.firstName} ${this.lastName}`;
},
// フィルターの代替例2: リストのフィルタリング/ソート
// Vue 2フィルターではリアクティビティに問題があった箇所
filteredItems() {
console.log("filteredItems computed property re-calculated");
const query = this.searchQuery.toLowerCase();
return this.items.filter(item => item.name.toLowerCase().includes(query));
},
// フィルターの代替例3: HTMLエスケープ
// filter: {{ rawHtml | escapeHtml }} のようなイメージ
// computedでは算出プロパティとして値を定義
escapedHtml() {
// 簡単なエスケープ処理の例
if (!this.rawHtml) return '';
return this.rawHtml
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
// フィルターの代替例4: 日付フォーマット (引数なしの場合)
// filter: {{ timestamp | formatDate }} のようなイメージ
formattedDate() {
console.log("formattedDate computed property re-calculated");
if (!this.timestamp) return '';
const date = new Date(this.timestamp);
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
return `${year}/${month}/${day}`;
},
// フィルターの代替例5: 通貨フォーマット (引数なしの場合)
// filter: {{ price | formatCurrency }} のようなイメージ
formattedPrice() {
console.log("formattedPrice computed property re-calculated");
if (typeof this.price !== 'number') return this.price;
const formatter = new Intl.NumberFormat('ja-JP', {
style: 'currency',
currency: 'JPY',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
return formatter.format(this.price);
}
}
};
テンプレートでの使用:
計算プロパティは、通常のデータプロパティと同じように参照します。パイプ |
は使いません。
```html
計算プロパティによるデータ整形
フルネーム: {{ fullName }}
- {{ item.name }} - {{ item.price }}
エスケープされたHTML:
日付(computed): {{ formattedDate }}
価格(computed): {{ formattedPrice }}
```
計算プロパティのメリット:
- リアクティビティ: 依存するリアクティブデータ(
data
、props
、他のcomputed
)が変更されると自動的に再計算され、表示が更新されます。これはVue 2フィルターの大きな制限を克服します。 - キャッシュ: 依存関係に変更がない限り、前回の計算結果がキャッシュされます。同じ値を複数回参照しても無駄な計算は行われません。パフォーマンス面で優れています。
this
へのアクセス: コンポーネントインスタンスのデータやメソッドにthis
経由でアクセスできます。コンポーネントの状態に基づいた複雑な整形が可能です。- 可読性: テンプレートがシンプルになります(
{{ formattedData }}
のように、算出された値の名前を参照するだけ)。整形ロジックはJavaScriptのcomputed
オプション内に分離されます。
計算プロパティのデメリット:
- 引数を直接渡せない: テンプレート側から計算プロパティに引数を直接渡すことはできません。もし動的に引数を変えて整形したい場合は、後述のメソッドを使うか、算出関数内でコンポーネントのデータプロパティなどを参照して動的な引数をシミュレートする必要があります。
- 純粋な整形以外の処理: 値を算出する以外の副作用(API呼び出し、DOM操作など)を持つ処理には向きません。
フィルターとの比較(計算プロパティ vs. filter):
特徴 | Vue 2 filter | Vue 3 computed property |
---|---|---|
主な用途 | テンプレートでの表示整形 | リアクティブデータからの算出、表示整形 |
構文(テンプレート) | {{ value | filterName(args) }} |
{{ computedPropertyName }} |
定義場所 | filters オプション (Vue.filter for global) |
computed オプション |
リアクティビティ | 入力値変更時のみ再評価 (Vue 2) | 依存するリアクティブデータ変更時に再評価 |
キャッシュ | なし (Vue 2) | あり |
this へのアクセス |
不可 | 可能 |
引数(テンプレートから) | 直接渡せる | 直接渡せない |
適切なケース | シンプルで独立した値整形 | 依存関係を持つ算出、リスト操作、キャッシュが必要な場合 |
6.2. メソッド (Methods)
メソッドは、コンポーネントに関連する関数を定義するのに使われます。イベントハンドラとしてよく使われますが、テンプレートから通常のJavaScript関数として呼び出すこともできます。
使い方、定義方法:
コンポーネントのオプションオブジェクトに methods
プロパティを追加し、その中にメソッド名と関数本体を定義します。
javascript
export default {
data() {
return {
product: {
name: 'Laptop',
price: 98000,
currencyCode: 'JPY'
},
longDescription: 'This is a very long description of the product...',
// ...
};
},
methods: {
// フィルターの代替例1: 引数を持つ複雑な整形 (例: 通貨フォーマット)
// filter: {{ product.price | formatCurrency(product.currencyCode, 2) }} のようなイメージ
formatCurrency(value, currencyCode = 'JPY', decimalPlaces = 0) {
console.log("formatCurrency method called");
if (typeof value !== 'number') return value;
const formatter = new Intl.NumberFormat(navigator.language, { // ユーザーのロケールを使用
style: 'currency',
currency: currencyCode,
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
});
return formatter.format(value);
},
// フィルターの代替例2: 引数を持つ文字列整形 (例: 切り詰め)
// filter: {{ longDescription | truncate(50, ' ... 続き') }} のようなイメージ
truncateText(value, length = 100, suffix = '...') {
console.log("truncateText method called");
if (!value) return '';
value = value.toString();
if (value.length <= length) {
return value;
}
return value.substring(0, length) + suffix;
},
// フィルターの代替例3: 条件に応じたクラス名の生成など
getStatusClass(status) {
const classMap = {
'pending': 'text-warning',
'completed': 'text-success',
'failed': 'text-danger',
};
return classMap[status] || 'text-secondary';
}
}
};
テンプレートでの使用:
メソッドは、テンプレート内で関数呼び出しのように記述します。
```html
メソッドによるデータ整形
価格: {{ formatCurrency(product.price, product.currencyCode, 0) }}
説明(切り詰め): {{ truncateText(longDescription, 80, ' >>>') }}
このテキストは成功状態です。
このテキストは失敗状態です。
このテキストは不明状態です。
```
メソッドのメリット:
- 引数を自由に渡せる: テンプレート側から必要な引数を渡すことができます。
this
へのアクセス: コンポーネントインスタンスのデータやメソッドにthis
経由でアクセスできます。- 柔軟性: 任意のJavaScriptコードを実行できるため、計算プロパティでは難しい複雑なロジックや副作用のある処理も記述できます(ただし、テンプレートから呼ばれるメソッドは副作用がない方が望ましいです)。
メソッドのデメリット:
- キャッシュされない: テンプレート内でメソッドが呼び出されるたびに必ず実行されます。計算プロパティのように結果はキャッシュされません。同じ入力値でも、テンプレートが再レンダリングされるたびにメソッドが実行される可能性があります。パフォーマンスに影響を与える可能性があるため、計算コストの高い処理には向きません。
- リアクティビティ: メソッド自体はリアクティブではありません。メソッド内で参照しているリアクティブデータが変更されても、テンプレートが再レンダリングされない限りメソッドは再実行されません。メソッドの実行は、依存するデータではなく、テンプレートの再レンダリングによって引き起こされます。
フィルターとの比較(メソッド vs. filter):
特徴 | Vue 2 filter | Vue 3 method |
---|---|---|
主な用途 | テンプレートでの表示整形 | イベント処理、テンプレートからの関数呼び出し |
構文(テンプレート) | {{ value | filterName(args) }} |
{{ methodName(value, args) }} |
定義場所 | filters オプション (Vue.filter for global) |
methods オプション |
リアクティビティ | 入力値変更時のみ再評価 (Vue 2) | なし(テンプレート再レンダリング時に実行) |
キャッシュ | なし (Vue 2) | なし |
this へのアクセス |
不可 | 可能 |
引数(テンプレートから) | 直接渡せる | 直接渡せる |
適切なケース | シンプルで独立した値整形 | 動的に引数を変えたい、計算コストが低い、イベント関連 |
6.3. JavaScriptによる直接的なデータ変換
非常に単純な整形や、リスト内の各要素に対して個別に行う変換であれば、テンプレート内のJavaScript式で直接行うことも可能です。主に v-for
の中で使われることがあります。
使い方:
テンプレートの {{ }}
や v-bind
式の中で、JavaScriptの関数やメソッドを直接呼び出します。
```html
JavaScriptによる直接的なデータ変換
ステータス: {{ status === 'active' ? 'アクティブ' : '非アクティブ' }}
メッセージ(大文字): {{ message.toUpperCase() }}
メッセージ(部分文字列): {{ message.substring(0, 5) + '...' }}
-
{{ item.name }}: {{ Math.floor(item.price) }}円
```
メリット:
- 非常にシンプル: 非常に単純なケースでは、別途計算プロパティやメソッドを定義する必要がなく、手軽に記述できます。
- ローカル: その場で完結するため、他の部分への影響がありません。
デメリット:
- 複雑さの限界: 少しでもロジックが複雑になると、テンプレートの可読性が著しく低下します。
- 再利用性なし: 同じ整形処理を別の場所で行いたい場合、コードをコピー&ペーストする必要があります。
- テストの難しさ: テンプレート内に直接記述されたロジックは、単体テストがしにくいです。
- 保守性: 後から修正や機能追加を行う際に、テンプレート内のロジックを探して変更するのが大変になります。
適切なケース:
- 非常にシンプルで一度しか使わない整形(例: 文字列のtoUpperCase()、簡単な条件分岐)。
v-for
内で各アイテムに対して行う、非常に簡単な変換(例:Math.floor()
)。
6.4. それぞれの使い分け
Vue 3以降では、これらの代替手段を適切に使い分けることが重要です。
-
計算プロパティ (Computed Properties):
- 最も推奨されるデータ整形方法。
- 依存するリアクティブデータに基づいて算出される値。
- キャッシュが必要な場合。
- リストのフィルタリングやソートなど、複数のリアクティブデータに依存する複雑な算出。
this
のデータやメソッドにアクセスする必要がある場合。- テンプレートの可読性を高く保ちたい場合。
- 引数をテンプレートから直接渡す必要がない場合。
-
メソッド (Methods):
- テンプレートから引数を動的に渡して整形したい場合。
- 計算プロパティほどキャッシュが重要ではない、あるいは計算コストが低い処理。
- イベントハンドラと共通の整形ロジックを使いたい場合。
this
のデータやメソッドにアクセスする必要がある場合。
-
JavaScriptによる直接的なデータ変換:
- ごく単純で一度きりの整形。
- テンプレートの可読性が損なわれない範囲内。
- 再利用性やテスト可能性が重要でない場合。
一般的には、まず計算プロパティで実現できないかを検討し、引数が必要な場合やキャッシュが不要な場合にメソッドを検討するという流れが良いでしょう。テンプレート内の直接記述は、あくまで最後の手段と考え、極力避けるのが保守性の高いコードを書く上でのベストプラクティスです。
7. filterから代替手段への移行ガイド (Vue 2からVue 3へ)
Vue 2でフィルターを extensively に使用している場合、Vue 3への移行にはそれらを代替手段に置き換える作業が必要です。ここでは、一般的な移行方法を示します。
7.1. ローカルフィルターの移行
ローカルフィルターは、定義されていたコンポーネント内に閉じた整形処理でした。多くの場合、これをそのコンポーネントの計算プロパティまたはメソッドに変換します。
元のVue 2コード:
javascript
// MyComponent.vue (Vue 2)
export default {
data() {
return {
productPrice: 12345.67,
longProductName: 'これは非常に長い商品名です。',
};
},
filters: {
// ローカルフィルター例1: 通貨フォーマット
formatCurrency(value, currency = '¥') {
if (typeof value !== 'number') return value;
const formatted = value.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return currency + formatted;
},
// ローカルフィルター例2: 文字列切り詰め
truncate(value, length = 20) {
if (!value) return '';
value = value.toString();
if (value.length <= length) return value;
return value.substring(0, length) + '...';
}
},
// ...
}
テンプレートでの使用 (Vue 2):
```html
価格: {{ productPrice | formatCurrency('$') }}
商品名: {{ longProductName | truncate(15) }}
```
Vue 3への移行:
ローカルフィルターの関数本体を、計算プロパティまたはメソッドとして computed
または methods
オプションに移動します。テンプレートでの使用箇所も、パイプ構文から計算プロパティの参照またはメソッド呼び出しに書き換えます。
- 引数がない、または固定引数でキャッシュしたい場合: 計算プロパティにする。
- 引数を動的に渡す必要がある、またはキャッシュが不要な場合: メソッドにする。
移行後のVue 3コード例:
```javascript
// MyComponent.vue (Vue 3)
export default {
data() {
return {
productPrice: 12345.67,
longProductName: 'これは非常に長い商品名です。',
};
},
computed: {
// ローカルフィルター formatCurrency の代替 (引数をデータとして持つか、固定の場合)
// 例: 通貨記号が固定の場合や、propsなどで渡される場合
// この例では、テンプレートで '$' を渡していたため、完全に同じ機能にするにはメソッドが良いが、
// もし通貨記号がthis.currencyCodeのようにデータならcomputedにできる
// 例として、ここでは小数点以下を固定0桁としてcomputedにする
formattedPriceComputed() {
if (typeof this.productPrice !== 'number') return this.productPrice;
const formatter = new Intl.NumberFormat('en-US', { // 例: USD形式
style: 'currency',
currency: 'USD', // 固定通貨コード
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
return formatter.format(this.productPrice);
},
// ローカルフィルター truncate の代替 (引数が固定、またはデータ依存の場合)
// この例ではテンプレートで引数を渡していたため、完全に同じ機能にするにはメソッドが良い
// 例として、ここでは切り詰め長さを固定20文字としてcomputedにする
truncatedProductNameComputed() {
const length = 20; // 固定
const value = this.longProductName;
if (!value) return '';
value = value.toString();
if (value.length <= length) return value;
return value.substring(0, length) + '...';
}
},
methods: {
// ローカルフィルター formatCurrency の代替 (引数をテンプレートから渡す場合)
formatCurrencyMethod(value, currency = '¥', decimalPlaces = 2) {
if (typeof value !== 'number') return value;
const formatter = new Intl.NumberFormat('ja-JP', { // ロケールや通貨コードを引数で指定
style: 'currency',
currency: currency === '$' ? 'USD' : 'JPY', // 引数に応じて通貨コードを切り替え
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
});
// Intl.NumberFormatは地域設定に基づいた通貨記号を自動で含めるため、currency引数は直接使わないことが多い
// もし引数の文字列をそのまま通貨記号として後置したい場合は別途ロジックが必要
// return value.toFixed(decimalPlaces).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + currency;
// こちらがIntl.NumberFormatを使った一般的な実装
return formatter.format(value);
},
// ローカルフィルター truncate の代替 (引数をテンプレートから渡す場合)
truncateMethod(value, length = 20, suffix = '...') {
if (!value) return '';
value = value.toString();
if (value.length <= length) return value;
return value.substring(0, length) + suffix;
}
}
};
```
テンプレートでの使用 (Vue 3):
```html
価格 (Computed): {{ formattedPriceComputed }}
商品名 (Computed): {{ truncatedProductNameComputed }}
価格 (Method): {{ formatCurrencyMethod(productPrice, '$', 2) }}
商品名 (Method): {{ truncateMethod(longProductName, 15) }}
商品名 (Method, 異なる引数): {{ truncateMethod(longProductName, 10, '...') }}
```
フィルターの引数が固定値だったり、そのコンポーネントのデータやプロパティに依存している場合は、計算プロパティへの移行が適しています。テンプレート側で動的に引数を変えたい場合は、メソッドへの移行が適しています。
7.2. グローバルフィルターの移行
グローバルフィルターは、アプリケーション全体で再利用されていました。これを代替するにはいくつかの方法があります。
-
単なるJavaScriptヘルパー関数にする:
最もシンプルで推奨される方法です。フィルター関数を独立したJavaScriptファイルに定義し、必要なコンポーネントでインポートして使用します。元のVue 2コード:
```javascript
// main.js (Vue 2)
import Vue from 'vue';
import App from './App.vue';Vue.filter('capitalize', function(value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});new Vue({ / ... / }).$mount('#app');
```移行後のVue 3コード:
```javascript
// utils/formatters.js (新しいファイル)
export function capitalize(value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}// 他のヘルパー関数もここに定義
// export function formatCurrency(...) { ... }
// export function formatDate(...) { ... }
```コンポーネントでの使用 (Vue 3):
```html
{{ capitalizedMessage }}
{{ capitalizeMethod('another string') }}
```ほとんどのグローバルフィルターは、引数を取らないか固定の引数で使われることが多いので、計算プロパティとしてラップするのが最もVue 3らしい使い方と言えます。テンプレートから引数を動的に渡したい場合は、メソッドとしてラップします。
-
Mixinとして定義する:
あまり推奨されませんが、複数のコンポーネントで同じ計算プロパティやメソッド群を使いたい場合に、Mixinsとしてまとめることもできます。ただし、Mixinsは名前の衝突や依存関係の追跡の難しさから、Vue 3ではComposable Functions(Composition API)の使用が推奨されています。 -
グローバルプロパティ/メソッドとして登録する:
Vue 3では、app.config.globalProperties
を使ってアプリケーションインスタンスにグローバルなプロパティやメソッドを追加できます。これにより、テンプレートから$filters.filterName(value, args)
のような形でアクセスできるようになります。ただし、これはテンプレート内でJavaScript式を長くすることになるため、可読性の観点からはあまり推奨されません。```javascript
// main.js (Vue 3)
import { createApp } from 'vue';
import App from './App.vue';
import { capitalize, formatCurrency } from './utils/formatters'; // ヘルパー関数をインポートconst app = createApp(App);
// グローバルなヘルパー関数を登録
app.config.globalProperties.$filters = {
capitalize, // capitalize: capitalize と同じ
formatCurrency // formatCurrency: formatCurrency と同じ
};app.mount('#app');
```コンポーネントでの使用 (Vue 3):
```html
{{ $filters.capitalize(message) }}
{{ $filters.formatCurrency(price, '$', 2) }}
```この方法は、Vue 2のフィルター構文に慣れた開発者がVue 3に移行する際に、一時的な代替手段として使用されることがあります。しかし、Vue 3の設計思想(テンプレートのシンプル化、JavaScript標準機能への回帰)からは少し外れるため、長期的な解決策としてはヘルパー関数 + computed/methods が推奨されます。
8. よくある質問 (FAQ)
8.1. filterはVue 3で本当に使えないのか?
はい、Vue 3では公式には廃止されており、コア機能としては提供されていません。テンプレートでパイプ |
を使ったフィルター構文は動作しません。
ただし、サードパーティのライブラリや、globalProperties
を使った前述のような方法で、似たような機能を再現することは可能ではあります。しかし、それはVue 3の推奨する書き方ではないため、特別な理由がない限り、計算プロパティやメソッドなどの代替手段を使うべきです。
8.2. ComputedとMethods、どちらを使うべきか?
「計算プロパティとメソッドの使い分け」セクションで詳しく解説した通りです。
- キャッシュが必要で、依存データがリアクティブであれば 計算プロパティ。
- 引数をテンプレートから動的に渡したい、またはキャッシュが不要/不向きであれば メソッド。
データ整形という文脈では、多くの場合、整形対象の値が変更されたときに再計算され、かつ同じ値に対して無駄な計算を繰り返さない計算プロパティが適しています。しかし、フォーマット文字列などをテンプレート側で動的に変えたい場合はメソッドが便利です。
8.3. パフォーマンスへの影響は?
Vue 2のフィルターはキャッシュされないため、特に v-for
内などで多数使用するとパフォーマンスの問題を引き起こす可能性がありました。
Vue 3で推奨される計算プロパティは依存するリアクティブデータに基づいてキャッシュされるため、パフォーマンス面で優れています。メソッドは呼び出されるたびに実行されますが、Vue 3ではテンプレートの再レンダリングメカニズムが改善されており、Vue 2のフィルターよりもパフォーマンスが悪化することは少ないでしょう。ただし、計算コストの高い処理をメソッドで行う場合は注意が必要です。
一般的に、大規模なデータセットの整形や、コンポーネント内の複数の場所で同じ整形を繰り返し行う場合は、計算プロパティを使うのがパフォーマンス的にも良い選択です。
8.4. サーバーサイドレンダリング (SSR) とfilter/computed/methods
Vue.jsのSSRでは、テンプレートの評価はサーバーサイドで行われます。
- Vue 2 filter: サーバーサイドでフィルター関数が実行されます。グローバルフィルターはサーバー側でも定義されている必要があります。
- Vue 3 computed/methods: サーバーサイドで計算プロパティやメソッドが実行されます。これらの関数が依存するデータや、中で呼び出すヘルパー関数などは、サーバーサイドの環境で実行可能である必要があります(例: ブラウザAPIに依存しない)。
どちらの場合も、サーバーサイドとクライアントサイドで同じ出力になるように、整形ロジックや依存するライブラリ(例: moment.jsなど)を両方の環境で適切に扱う必要があります。特に日付や通貨など、ユーザーのロケールに依存する整形を行う場合は注意が必要です。Intl.NumberFormat
や Intl.DateTimeFormat
のような国際化APIは、Node.js環境でも利用可能ですが、利用可能なロケールデータに違いがある場合があります。
9. まとめ
この記事では、Vue.jsのフィルター機能について、その基本的な使い方、ローカル/グローバル定義、応用例、そして内部動作を詳しく解説しました。フィルターはテンプレート内で手軽に表示データを整形できる便利な機能でしたが、Vue 2の時点ですでにいくつかの制限(this
へのアクセス不可、リアクティビティの制限、キャッシュなし)がありました。
そして、Vue 3ではこれらの制限やテンプレート構文の可読性、そして既存のJavaScript機能(計算プロパティ、メソッド)で十分に代替可能であることから、フィルター機能は非推奨・廃止となりました。
Vue 3以降のデータ整形においては、主に以下の手段が推奨されます。
- 計算プロパティ (Computed Properties): 依存関係を持つリアクティブな算出、キャッシュが必要な場合に最も適しています。
- メソッド (Methods): テンプレートから動的に引数を渡したい場合、またはキャッシュが不要な場合に適しています。
- JavaScriptによる直接的なデータ変換: ごく単純で一度きりの整形に限定的に使用します。
Vue 2からVue 3への移行を行う際は、既存のフィルターをこれらの代替手段(特に計算プロパティやメソッド)に置き換える作業が必須となります。多くの場合は、フィルター関数を抽出して独立したヘルパー関数とし、それをコンポーネント内の計算プロパティやメソッドから呼び出す形にリファクタリングするのが推奨されるパターンです。
フィルター機能はVue.jsの進化の過程で役割を終えましたが、その概念や、表示データの整形が必要であるという点は変わりません。Vue 3以降のベストプラクティスを理解し、状況に応じて計算プロパティやメソッドを適切に使い分けることで、より効率的で保守性の高いVue.jsアプリケーション開発が可能になります。
この記事が、Vue.jsにおけるデータ整形の方法について、過去から現在までの変遷を含めた深い理解の一助となれば幸いです。