mcpField
C
container-presentational

container-presentational

github.com/patternsdev開発257195 回インストール28 週間前に更新
リポジトリ

container-presentational について

container-presentational は、データ取得と振る舞いをコンテナに、描画をプレゼンテーションコンポーネントに分ける Vue のコンポーネント設計パターンを解説します。Vue 3 では、追加のラッパー層を設けずに同様の分離を行う composable も紹介します。

主な機能

  1. 01コンテナと表示コンポーネントの分離
  2. 02props によるデータとコールバックの受け渡し
  3. 03表示コンポーネントの描画への集中
  4. 04代替手段としての composable の紹介

活用シーン

  1. 01データ取得と UI 描画の分離
  2. 02表示コンポーネントの再利用とテスト
  3. 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つのカテゴリに分けるパターンを紹介しました。

  1. プレゼンテーションコンポーネント(またはダムコンポーネント):見た目を担当します。データの読み込み方法や変更方法は定義せず、データとコールバックを props 経由でのみ受け取ります。
  2. コンテナコンポーネント(またはスマートコンポーネント):動作を担当します。プレゼンテーションコンポーネントや他のコンテナコンポーネントに、データと振る舞いを提供します。

使用する場面

  • データ取得ロジックと UI のレンダリングを明確に分離したい場合に使用します
  • プレゼンテーションコンポーネントを再利用しやすく、テストしやすくするのに役立ちます

手順

  • コンテナコンポーネントはデータ取得と状態を扱い、プレゼンテーションコンポーネントは props 経由でレンダリングを扱います
  • Vue 3 では、同じ関心事の分離を実現するために、コンテナコンポーネントよりコンポーザブルを優先します
  • プレゼンテーションコンポーネントはステートレスに保ちます。データは props を通してのみ受け取ります
  • コンテナによるラッパーの現代的な代替手段として、useDogImages() コンポーザブルパターンを使用します

詳細

このパターンは主に React と関連付けられていましたが、その基本原則は、ほかのさまざまなライブラリやフレームワークでも、さまざまな形で採用され、応用されてきました。

Dan によるこの区別は、JavaScript アプリケーションを構成するための、より明確でスケーラブルな方法を示しました。異なる種類のコンポーネントの責任を明確に定義することで、開発者は UI コンポーネント(プレゼンテーション)とロジック(コンテナ)の再利用性を高められます。

しかし、React のフックや Vue 3 の Composition API が登場すると、プレゼンテーションコンポーネントとコンテナコンポーネントの明確な境界は曖昧になり始めました。フックと Composition API によって、開発者はクラスベースのコンテナコンポーネントや Options API に必ずしも限定されずに、状態やロジックをカプセル化して再利用できるようになりました。とはいえ、このパターンが役立つ場面は今でもあります。

API から6枚の犬の画像を取得し、画面に表示するアプリケーションを作成するとします。

コンテナ/プレゼンテーションパターンに従うには、この処理を2つの部分に分けて、関心事を分離します。

  1. プレゼンテーションコンポーネント:ユーザーにデータを_どのように_表示するかを扱うコンポーネントです。この例では、犬の画像のリストをレンダリングします。
  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 コンポーザブルを使っても、アプリケーションロジックとビューは分離されたままです。コンポーネント内でデータを変更せず、コンポーザブルが返すデータを使っているだけです。

コンテナ/プレゼンテーションパターンと同様に、コンポーザブルを使うと、コンポーネント内のロジックとビューを簡単に分離できます。プレゼンテーションコンポーネントをコンテナコンポーネントでラップするために必要だった余分な階層をなくせます。

ソース

参考資料

コメント