data-provider について
data-provider は、データ取得と状態管理を表示から分離する Vue 向けデータプロバイダーパターンのガイドです。レンダーレスコンポーネントとスコープ付きスロットを使い、複数のコンポーネントでデータ処理を共有しながら異なる表示を実現します。
主な機能
- 01データ取得と状態管理のカプセル化
- 02スコープ付きスロットによるデータ受け渡し
- 03子コンポーネントへの読み込み状態と操作の提供
- 04表示とデータ処理の分離
活用シーン
- 01複数コンポーネントで同じデータを異なる形で表示
- 02データ取得処理の集約
- 03データ処理を変えずに表示を差し替え
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ patternsdev/ skills --skill data-providerインストール後、Claude Code で/data-providerを入力して読み込まれたか確認します。
詳細
データプロバイダーパターン
目次
前回の記事では、レンダーレスコンポーネントを使うと、コンポーネントのロジックと表示を分離できることを学びました。これは、さまざまな UI 実装に適用できる再利用可能なロジックを作成する際に役立ちます。
レンダーレスコンポーネントを使うと、データプロバイダーパターンとして知られる、もうひとつの便利なパターンも活用できます。
使用する場面
- 複数のコンポーネントで同じデータを利用しつつ、それぞれ異なる方法で表示する必要がある場合に使用します
- 特定の UI コンポーネントに依存させずに、データ取得ロジックを一元化するのに役立ちます
使用しない場面
- コンポーザブルで余分なコンポーネント層を追加せずにデータロジックを処理できる場合(Vue 3 以降)
- データを利用するコンポーネントが 1 つだけの場合。コンポーザブルやインラインでの取得のほうがシンプルです
- データプロバイダーのネストによって間接性が増し、テンプレートが追いにくくなる場合
手順
- テンプレートにスコープ付きスロットプロパティを持つ単一の
<slot>だけを配置した、データプロバイダーコンポーネントを作成します - データ、読み込み状態、アクションメソッドをスコープ付きスロットプロパティとして子コンポーネントに渡します
- 親で
v-slotの分割代入を使い、提供されたデータにアクセスします - 子コンポーネントは表示のみに専念させ、すべてのデータロジックはデータプロバイダーで処理します
詳細
データプロバイダーパターン
データプロバイダーパターンは、データのレンダリング方法や表示方法を気にすることなくコンポーネントにデータと状態管理の機能を提供することに重点を置き、Vue のレンダーレスコンポーネントパターンを補完するデザインパターンです。
データプロバイダーパターンでは、データプロバイダーコンポーネントが、子コンポーネントにデータを取得、管理、公開するロジックをカプセル化します。子コンポーネントはこのデータを受け取り、それぞれのレンダリングや動作に利用できます。
このパターンでは、データプロバイダーコンポーネントがデータ関連のタスクを担い、子コンポーネントがプレゼンテーションとインタラクションに集中できるため、関心事の分離が促進されます。
例を使って、データプロバイダーパターンを説明しましょう。ジョークの前振りとオチを表示するシンプルなアプリケーションを考えてみてください。この例を自己完結させるため、外部 API に依存するのではなく、ローカルのインメモリデータソースを使用します。
const jokes = [
{ id: 1, setup: "Why did the dev go broke?", punchline: "Because they used up all their cache." },
{ id: 2, setup: "Why do functions love TypeScript?", punchline: "Because it keeps their arguments in order." },
];
まず、ジョークの読み込みを担当する DataProvider というデータプロバイダーコンポーネントを作成します。コンポーネントの <script> セクションでは、Vue から ref() 関数と reactive() 関数をインポートし、ローカルデータソースを定義して、選択されたジョークと読み込み状態を保持する data と loading のリアクティブプロパティを設定します。
<script setup>
import { ref, reactive } from "vue";
const jokes = [
{ id: 1, setup: "Why did the dev go broke?", punchline: "Because they used up all their cache." },
{ id: 2, setup: "Why do functions love TypeScript?", punchline: "Because it keeps their arguments in order." },
];
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
</script>
次に、DataProvider コンポーネント内に fetchJoke() 関数を作成し、非同期でデータを読み込む動作を再現します。
const fetchJoke = async () => {
loading.value = true;
try {
await new Promise((resolve) => setTimeout(resolve, 300));
const jokeData = jokes[Math.floor(Math.random() * jokes.length)];
data.setup = jokeData.setup;
data.punchline = jokeData.punchline;
} catch (error) {
console.error("Error loading joke:", error);
} finally {
loading.value = false;
}
};
フェッチ関数の準備ができたら、onMounted() ライフサイクルフックを使って、コンポーネントのマウント時に呼び出します。
import { ref, reactive, onMounted } from "vue";
// ...
onMounted(() => {
fetchJoke();
});
データプロバイダーコンポーネントの重要な要素は、そのテンプレートが単一の <slot> 要素のみで構成されていることです。このスロットは、取得したデータと関連するメソッドをスコープ付きスロットを使って子コンポーネントに提供します。
<template>
<slot :data="data" :loading="loading" :fetchJoke="fetchJoke"></slot>
</template>
DataProvider コンポーネントは、data、loading、fetchJoke をスコープ付きスロットのプロパティとして渡します。つまり、DataProvider 内に配置された子コンポーネントは、これらのプロパティにアクセスできます。
次に、ジョークのデータを表示する JokeCard コンポーネントを作成しましょう。
<template>
<div class="joke-card">
<p v-if="loading">Loading...</p>
<div v-else>
<p class="setup">{{ data.setup }}</p>
<p class="punchline">{{ data.punchline }}</p>
</div>
<button @click="fetchJoke">Get Another Joke</button>
</div>
</template>
<script setup>
defineProps(["data", "loading", "fetchJoke"]);
</script>
JokeCard コンポーネントは、シンプルなプレゼンテーション用コンポーネントです。data、loading、fetchJoke をプロパティとして受け取り、ジョークのデータと新しいジョークを取得するボタンを表示します。
では、App コンポーネントで DataProvider コンポーネントを使い、すべてを組み合わせましょう。DataProvider の内側に JokeCard コンポーネントを配置し、スコープ付きスロットのプロパティを渡します。
<template>
<DataProvider v-slot="{ data, loading, fetchJoke }">
<JokeCard :data="data" :loading="loading" :fetchJoke="fetchJoke" />
</DataProvider>
</template>
<script setup>
import DataProvider from "./components/DataProvider.vue";
import JokeCard from "./components/JokeCard.vue";
</script>
この構成では、DataProvider がデータの取得と管理をすべて担当し、JokeCard はデータの表示だけに集中します。このように明確に分離することで、データ取得ロジックに手を加えることなく、プレゼンテーション用コンポーネントを別のものに簡単に差し替えられます。
データプロバイダーパターンは、特に次のような場合に役立ちます。
- 複数のコンポーネントで同じデータを利用しながら、それぞれ異なる方法で表示する必要がある場合。
- 特定の UI コンポーネントと密接に結び付けずに、データ取得ロジックを一元化したい場合。
- コンポーネントを単一の責務に集中させたい場合。