container-presentational について
container-presentational は、データ取得と振る舞いをコンテナに、描画をプレゼンテーションコンポーネントに分ける Vue のコンポーネント設計パターンを解説します。Vue 3 では、追加のラッパー層を設けずに同様の分離を行う composable も紹介します。
主な機能
- 01コンテナと表示コンポーネントの分離
- 02props によるデータとコールバックの受け渡し
- 03表示コンポーネントの描画への集中
- 04代替手段としての composable の紹介
活用シーン
- 01データ取得と UI 描画の分離
- 02表示コンポーネントの再利用とテスト
- 03Vue 3 composable によるロジック整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ patternsdev/ skills --skill container-presentationalインストール後、Claude Code で/container-presentationalを入力して読み込まれたか確認します。
詳細
コンテナ/プレゼンテーションパターン
目次
2015年、Dan Abramov は「Presentational and Container Components」という記事を執筆し、React におけるコンポーネントアーキテクチャについて、多くの開発者の考え方を変えました。彼はコンポーネントを次の2つのカテゴリに分けるパターンを紹介しました。
- プレゼンテーションコンポーネント(またはダムコンポーネント):見た目を担当します。データの読み込み方法や変更方法は定義せず、データとコールバックを props 経由でのみ受け取ります。
- コンテナコンポーネント(またはスマートコンポーネント):動作を担当します。プレゼンテーションコンポーネントや他のコンテナコンポーネントに、データと振る舞いを提供します。
使用する場面
- データ取得ロジックと UI のレンダリングを明確に分離したい場合に使用します
- プレゼンテーションコンポーネントを再利用しやすく、テストしやすくするのに役立ちます
手順
- コンテナコンポーネントはデータ取得と状態を扱い、プレゼンテーションコンポーネントは props 経由でレンダリングを扱います
- Vue 3 では、同じ関心事の分離を実現するために、コンテナコンポーネントよりコンポーザブルを優先します
- プレゼンテーションコンポーネントはステートレスに保ちます。データは props を通してのみ受け取ります
- コンテナによるラッパーの現代的な代替手段として、
useDogImages()コンポーザブルパターンを使用します
詳細
このパターンは主に React と関連付けられていましたが、その基本原則は、ほかのさまざまなライブラリやフレームワークでも、さまざまな形で採用され、応用されてきました。
Dan によるこの区別は、JavaScript アプリケーションを構成するための、より明確でスケーラブルな方法を示しました。異なる種類のコンポーネントの責任を明確に定義することで、開発者は UI コンポーネント(プレゼンテーション)とロジック(コンテナ)の再利用性を高められます。
しかし、React のフックや Vue 3 の Composition API が登場すると、プレゼンテーションコンポーネントとコンテナコンポーネントの明確な境界は曖昧になり始めました。フックと Composition API によって、開発者はクラスベースのコンテナコンポーネントや Options API に必ずしも限定されずに、状態やロジックをカプセル化して再利用できるようになりました。とはいえ、このパターンが役立つ場面は今でもあります。
API から6枚の犬の画像を取得し、画面に表示するアプリケーションを作成するとします。
コンテナ/プレゼンテーションパターンに従うには、この処理を2つの部分に分けて、関心事を分離します。
- プレゼンテーションコンポーネント:ユーザーにデータを_どのように_表示するかを扱うコンポーネントです。この例では、犬の画像のリストをレンダリングします。
- コンテナコンポーネント:ユーザーに_どの_データを表示するかを扱うコンポーネントです。この例では、犬の画像を取得します。
犬の画像を取得する処理はアプリケーションロジックに関係します。一方、画像の表示はビューだけに関係します。
プレゼンテーションコンポーネント
プレゼンテーションコンポーネントは props 経由でデータを受け取ります。主な役割は、スタイルを含め、受け取ったデータを変更することなく、望みどおりに表示することだけです。
犬の画像をレンダリングする際は、API から取得した各犬の画像を単純に反復処理して、表示します。props 経由でデータを受け取り、受け取ったデータをレンダリングする DogImages コンポーネントを作成できます。
<template>
<div>
<div v-for="(dog, index) in dogs" :key="index">
<img :src="dog" alt="Dog" />
</div>
</div>
</template>
<script setup>
defineProps(["dogs"]);
</script>
DogImages コンポーネントはプレゼンテーションコンポーネントです。プレゼンテーションコンポーネントは通常ステートレスで、UI のために状態が必要な場合を除き、独自の Vue の状態を持ちません。プレゼンテーションコンポーネントはコンテナコンポーネントからデータを受け取ります。
コンテナコンポーネント
コンテナコンポーネントの主な役割は、コンテナコンポーネントが内包するプレゼンテーションコンポーネントにデータを渡すことです。コンテナコンポーネント自身は通常、データを必要とするプレゼンテーションコンポーネント以外のコンポーネントをレンダリングしません。自身では何もレンダリングしないため、スタイルも含まないのが一般的です。
このデータを取得し、画面に表示するためにプレゼンテーションコンポーネント DogImages に渡すコンテナコンポーネントを作成する必要があります。
<template>
<DogImages :dogs="dogs" />
</template>
<script setup>
import { ref, onMounted } from "vue";
import DogImages from "./DogImages.vue";
const dogs = ref([]);
onMounted(async () => {
const response = await fetch(
"https://dog.ceo/api/breed/labrador/images/random/6"
);
const { message } = await response.json();
dogs.value = message;
});
</script>
これら2つのコンポーネントを組み合わせることで、アプリケーションロジックの処理とビューを分離できます。
コンポーザブル
多くの場合、コンテナ/プレゼンテーションパターンはコンポーザブルで置き換えられます。Composition API の導入により、開発者は状態を提供するコンテナコンポーネントを必要とせずに、簡単に状態を持たせられるようになりました。
コンテナコンポーネントにデータ取得ロジックを置く代わりに、画像を取得して犬の配列を返すカスタムコンポーザブルを作成できます。
import { ref, onMounted } from "vue";
export function useDogImages() {
const dogs = ref([]);
onMounted(async () => {
const response = await fetch(
"https://dog.ceo/api/breed/labrador/images/random/6"
);
const { message } = await response.json();
dogs.value = message;
});
return { dogs };
}
このコンポーザブルを使うことで、データを取得するラッパーとなるコンテナコンポーネントは不要になります。代わりに、プレゼンテーションコンポーネント DogImages でこのコンポーザブルを直接使えます!
<template>
<div>
<div v-for="(dog, index) in dogs" :key="index">
<img :src="dog" alt="Dog" />
</div>
</div>
</template>
<script setup>
import { useDogImages } from "../composables/useDogImages";
const { dogs } = useDogImages();
</script>
useDogImages コンポーザブルを使っても、アプリケーションロジックとビューは分離されたままです。コンポーネント内でデータを変更せず、コンポーザブルが返すデータを使っているだけです。
コンテナ/プレゼンテーションパターンと同様に、コンポーザブルを使うと、コンポーネント内のロジックとビューを簡単に分離できます。プレゼンテーションコンポーネントをコンテナコンポーネントでラップするために必要だった余分な階層をなくせます。