こんにちは。GMOメイクショップの森です。 弊社のシステム makeshopでは日々アーキテクチャ改善を進めています。本記事では、その一環として導入を進めている Container/Presentationalパターン について、実装例を交えながらそのメリット・デメリットや Vue 3 における実践的なポイントを解説します。
過去にもmakeshopのContainer/Presentationalパターン導入について解説した記事がありますので、こちらもあわせてご覧ください。
- Container/Presentationalパターンとは?
- Container/Presentationalパターンのメリット
- Container/Presentationalパターンのデメリットと注意点
- makeshopでの実装例
- Vue 3における実践と考慮すべきポイント
- まとめ
Container/Presentationalパターンとは?
Container/Presentationalパターンは、フロントエンド開発においてUIとロジックを分離するコンポーネント設計の有効な手法です。このパターンでは、コンポーネントの役割を以下の2つに明確に分離します。
- Containerコンポーネント:ロジックやデータ取得を担当し、Presentationalコンポーネントにデータを渡します
- Presentationalコンポーネント:UIの表示に専念し、受け取ったデータの描画とスタイリングを行います
この分離によってコンポーネントの責務が明確化され、再利用性やテストのしやすさが向上します。
Container/Presentationalパターンのメリット
このパターンを採用する主なメリットは以下の通りです。
責務の明確化
UIとロジックの分離により、コードの可読性が向上し、開発者間の認識が統一されます。再利用性の向上
Presentationalコンポーネントは、受け取るデータのみに依存するため、様々な場面で再利用できます。テスト容易性の改善
UIとロジックが分離され、個々のコンポーネントを独立してテストできます。メンテナンス性の向上
変更の影響範囲が限定され、大規模アプリケーションでも保守性が保たれます。
Container/Presentationalパターンのデメリットと注意点
一方で、以下のような注意点もあります。
初期開発コストの増加
コンポーネント分割により、初期実装に時間がかかります。ファイル数の増加
細かく分割しすぎるとファイル数が増え、管理が煩雑になる可能性があります。オーバーエンジニアリングのリスク
小規模プロジェクトでは、過剰な分割が逆に複雑さを増す場合があります。
makeshopでの実装例
makeshopで実装した「運営者情報ページ」を例に紹介します。このページは種別(法人・個人)と状態(入力・確認)によって以下の4種類の画面が存在します
法人の入力画面
個人の入力画面
法人の変更内容確認画面
個人の変更内容確認画面
コンポーネントの分離例
以下は入力画面のContainer/Presentational構成の一部抜粋です。
// AccountOwnerEdit.vue(編集画面Container) <script setup lang="ts"> import type { AccountOwner } from '@/services/console/pages/account/accountOwner/composables/useAccountOwner.ts' // 省略 const accountOwner = defineModel<AccountOwner>('accountOwner', { required: true }) </script> <template> <AccountOwnerLayoutPresentational> <template #header> <AccountOwnerEditHeader /> </template> <template #body> <AccountOwnerEditCorporate v-if="accountOwner.isCorporate" v-model:account-owner="accountOwner" /> <AccountOwnerEditIndividual v-else v-model:account-owner="accountOwner" /> </template> <template #bottom> <AccountOwnerEditBottom/> </template> </AccountOwnerLayoutPresentational> </template> // AccountOwnerLayoutPresentational.vue <template> <div class="page"> <slot name="header" /> <div class="mt-12"> <SectionsSlot class="mt-4"> <slot name="body" /> </SectionsSlot> <slot name="bottom" /> </div> </div> </template>
従来の実装では、単一のコンポーネント内に複数の条件分岐が存在し、複雑で保守性の低い構造になっていましたが、 Container/Presentationalパターンを導入することで、次のような改善を実現できました
責務の明確化
再利用性の向上
ファイル構成の変換
ここでは、Container/Presentationalパターン導入前後のファイル構成の違いを比較します。
従来のパターン(5ファイル):
account/ ├── AccountOwner.vue ├── composables/ └── parts/ └── AccountOwner/ ├── AccountOwnerAdminInfo.vue ├── AccountOwnerOperatorInfo.vue ├── AccountOwnerRegistrationInfo.vue └── AccountOwnerUserType.vue
Container/Presentationalパターン(22ファイル):
accountOwner/ ├── AccountOwner.vue ├── composables/ ├── container/ │ └── edit/ (入力画面関連) │ ├── AccountOwnerEdit.vue │ └── (他9ファイル) │ └── confirm/ (確認画面関連) │ ├── AccountOwnerConfirm.vue │ └── (他9ファイル) └── presentational/ ├── AccountOwnerFormCorporateLayoutPresentational.vue ├── AccountOwnerFormIndividualLayoutPresentational.vue └── AccountOwnerLayoutPresentational.vue
課題と対策
実装を進める中で直面した主な課題は「コンポーネント分割の粒度」の判断です。パターンに忠実すぎると、必要以上にファイル数が増え、管理が煩雑になる恐れがあります。
対策として
- Presentationalコンポーネントは3ファイルに絞り込み
- 「画面全体のレイアウト」と「法人/個人の表示差異」に焦点を当てる
- ファイル数の爆発的な増加を抑えつつ、パターンの恩恵を受けるバランスを取る
このように、適切な粒度を見極めることで、メンテナンス性と再利用性の両立を実現しました。
Vue 3における実践と考慮すべきポイント
Vue 3では、Composition APIやComposablesの登場により、従来のContainer/Presentationalパターンの役割をより柔軟に実現できるようになりました。
たとえば、ビジネスロジックをComposablesに抽出することで、それぞれのコンポーネントの役割をより明確化することも可能です。
また、実際の開発現場では、具体的なファイル構成や命名規則はチームやプロジェクトごとに異なります。 本記事では具体的なディレクトリ構造やファイル名は割愛しますが、重要なのは「UIとロジックの明確な分離」と「各コンポーネントの責務に基づいた設計」を意識することです。 これにより、変更が発生した際の影響範囲を限定し、各コンポーネントの再利用性やテスト容易性を高めることができます。
まとめ
Container/Presentationalパターンは、UIとロジックの責務を明確に分離することで、再利用性、テスト容易性、保守性の向上に大きく寄与します。
Vue 3では、Composition APIやComposablesとの組み合わせにより、このパターンのメリットをさらに引き出すことが可能です。
ただし、プロジェクトの規模や複雑さに応じて適用範囲を見極め、過剰な分割による管理負荷やオーバーエンジニアリングを避ける工夫が必要です。
今後、開発規模の大きなプロジェクトやチーム開発においては、このパターンを柔軟に取り入れることで、コードの一貫性と保守性を高めることができるでしょう。
Vue 3における柔軟かつ拡張性の高いコンポーネント設計として、非常に有効な設計手法の一つです。
是非プロジェクトの特性に合わせてContainer/Presentationalパターンの適用を検討してみてください