every Tech Blog

株式会社エブリーのTech Blogです。

Pinia Colada入門:非同期処理をもっとスムーズに!

はじめに

この記事は every Tech Blog Advent Calendar 2024 の2日目の記事です。

はじめまして、エブリーの羽馬(@naoki_haba)です。

Vue.js アプリケーションを開発していて、次のような課題に直面したことはありませんか?

  • API 通信のたびに似たようなコードを書くのが面倒
    • 例:ユーザー情報の取得や商品リストの取得など、毎回似た処理を書く必要がある。
  • ローディングやエラー処理を丁寧に書こうとするとコードが膨れ上がる
  • 複数の API コールやキャッシュを効率よく管理したい

そんな課題を解決するために登場したのが Pinia Colada です。

この記事では、Pinia Colada の類似機能を持つ他のライブラリと比較した上で、Pinia Colada の特徴や導入方法、基本的な使い方を紹介します。

  • 本記事は以下の登壇資料を元に執筆しています。こちらもぜひご覧ください:
  • 記事内で紹介するサンプルコードは、StackBlitz で試せるようにしています

Pinia Colada とは

Pinia Colada は、Vue.js アプリケーションにおける 非同期処理データ管理 を効率化するためのライブラリです。

次のような機能を提供します:

  • API リクエストの処理、キャッシュ管理、エラー処理を自動化
  • 冗長なボイラープレートコードを削減
  • Vueのリアクティブなデータ管理と自然に統合
  • 重複リクエストの防止やキャッシュ戦略の実装
  • DevTools対応: Vue DevToolsとの連携により、デバッグと状態の検査が容易になります
  • TypeScriptサポート: 優れたTypeScriptサポートにより、型安全性と開発体験を向上させます

Pinia Colada は、Vue エコシステムに最適化された軽量なライブラリであり、人気の高い TanStack Query から一部 API を取り入れています。そのため、TanStack Query に馴染みがある方には特に使いやすい設計です。

また、公式サイトには TanStack Query からの移行ガイド が用意されているため、既存プロジェクトでもスムーズに導入できます。

pinia-colada.esm.dev

非同期処理の課題

非同期処理はフロントエンド開発で避けられない重要な要素ですが、アプリケーションが大規模化するにつれ、次のような課題が生じることがあります。

1. 冗長なコード

API通信のたびに、ローディング状態やエラーハンドリングを記述する必要があります。以下のようなコードを何度も書いた経験はありませんか?

サンプルコード

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const isLoading = ref(false)
const error = ref<Error | null>(null)
const data = ref<any>(null)

async function fetchData() {
  isLoading.value = true
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1')
    if (!response.ok) throw new Error('データの取得に失敗しました')
    data.value = await response.json()
  } catch (e) {
    error.value = e as Error
  } finally {
    isLoading.value = false
  }
}
</script>

このようなコードを何度も書くのは、時間もかかり、ミスの温床になります。

2. 状態管理の複雑さ

非同期処理には、次のような状態を適切に管理する必要があります:

  • ローディング中かどうか
  • エラーの内容
  • データが最新かどうか
  • キャッシュの有効期限や再取得のタイミング

これらをすべて手動で管理すると、コードが複雑化しやすくなります。

3. 効率的なデータフェッチング

効率的に非同期処理を扱うには、以下が重要です:

  • 重複リクエストの防止:同じデータを何度も取得しない仕組み
  • キャッシュの活用:既存データを再利用して無駄なリクエストを削減
  • 自動再取得:データの期限切れやネットワークの復帰時に再フェッチ

これらを実現するのは容易ではありません。

非同期処理における様々なアプローチ

Vue.js には、非同期処理を扱うための便利なライブラリがいくつも存在します。それぞれの特徴を簡単に紹介します。

1. Vue Promised

Vue Promised は、Vue コンポーネント内で非同期状態を簡単に管理できるライブラリです。テンプレートでローディング中やエラー、結果の表示を切り替えることができます。

サンプルコード

<script setup lang="ts">
import { ref } from 'vue'
import { Promised } from 'vue-promised'

interface UserData {
  id: number
  name: string
  email: string
}

const userPromise = ref<Promise<UserData>>(
  fetch('https://jsonplaceholder.typicode.com/users/1')
    .then(r => {
      if (!r.ok) throw new Error('データの取得に失敗しました')
      return r.json()
    })
)
</script>

<template>
  <div>
    <Promised :promise="userPromise">
      <template #pending>
        <div>読み込み中...</div>
      </template>

      <template #rejected="error">
        <div>エラーが発生しました: {{ error.message }}</div>
      </template>

      <template #default="data">
        <div>
          <h2>ユーザー情報:</h2>
          <p>名前: {{ data.name }}</p>
          <p>メール: {{ data.email }}</p>
          <p>ユーザーID: {{ data.id }}</p>
        </div>
      </template>
    </Promised>
  </div>
</template>

2. swrv

swrv は、Stale-While-Revalidate パターンを採用したデータフェッチングライブラリです。一度取得したデータをキャッシュとして保持しながら、新しいデータをバックグラウンドで更新します。

サンプルコード

<script setup lang="ts">
import useSWRV from 'swrv';

interface UserData {
  id: number;
  name: string;
  email: string;
}

const { data, error } = useSWRV<UserData>(
  'https://jsonplaceholder.typicode.com/users/1',
  async (key) => {
    const res = await fetch(key);
    if (!res.ok) throw new Error('データの取得に失敗しました');
    return res.json();
  }
);
</script>

<template>
  <div>
    <div v-if="!data && !error">読み込み中...</div>

    <div v-else-if="error">エラーが発生しました: {{ error.message }}</div>

    <div v-else>
      <h2>ユーザー情報:</h2>
      <p>名前: {{ data.name }}</p>
      <p>メール: {{ data.email }}</p>
      <p>ユーザーID: {{ data.id }}</p>
    </div>
  </div>
</template>

3. TanStack Query (Vue Query)

TanStack Query は、非常に強力な非同期データ管理ライブラリです。複雑なクエリシナリオやキャッシュ管理が可能で、大規模なプロジェクトにも適しています。

サンプルコード

<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query';

interface UserData {
  id: number;
  name: string;
  email: string;
}

const { data, isPending, error } = useQuery<UserData>({
  queryKey: ['user', 1],
  queryFn: async () => {
    const res = await fetch('https://jsonplaceholder.typicode.com/users/1');
    if (!res.ok) throw new Error('データの取得に失敗しました');
    return res.json();
  },
});
</script>

<template>
  <div>
    <div v-if="isPending">読み込み中...</div>

    <div v-else-if="error">エラーが発生しました: {{ error.message }}</div>

    <div v-else-if="data">
      <h2>ユーザー情報:</h2>
      <p>名前: {{ data.name }}</p>
      <p>メール: {{ data.email }}</p>
      <p>ユーザーID: {{ data.id }}</p>
    </div>
  </div>
</template>

Pinia Colada の優位性

他のライブラリも非常に優れていますが、Pinia Colada は Vue.js 開発者に特化した設計で、特に以下の点が魅力です。

  • Pinia との統合
    Pinia Coladaは、非同期処理やキャッシュ管理を効率化するためにPiniaストアを直接活用する設計になっています。この一元化により、状態管理とデータフェッチのロジックを分散させることなく、簡潔で読みやすいコードを実現します。
  • Vue DevTools との統合
    Pinia Coladaは、Vue DevToolsと完全に統合されています。これにより、アプリケーション内の非同期処理やキャッシュの状態をリアルタイムで可視化できます。
    • 状態のトラッキングが簡単:現在のキャッシュの有効期限やフェッチのステータスを一目で確認可能。
    • デバッグが容易:エラーの発生箇所やAPIコールの詳細を素早く特定できます。
  • シンプルなAPI
    Vue のリアクティビティシステムを活用した直感的なAPIにより、ボイラープレートコードが大幅に削減されます。非同期処理の際にありがちなtry-catchやローディング状態の管理を意識せずに済みます。
  • 柔軟な設定
    デフォルトの設定でも十分に便利ですが、プロジェクトの要件に応じてキャッシュの戦略やエラー処理の挙動を柔軟にカスタマイズできます。これにより、小規模なアプリから大規模なエンタープライズ向けプロジェクトまで対応可能です。

Pinia Colada の基本的な使い方

それでは、Pinia Colada の導入手順と基本的な使い方を見ていきましょう。

セットアップ

Pinia と Pinia Colada をインストールします:

# npm
npm install pinia @pinia/colada

# yarn
yarn add pinia @pinia/colada

# pnpm
pnpm install pinia @pinia/colada

次に、main.ts で Pinia と Pinia Colada をアプリケーションに組み込みます:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { PiniaColada } from '@pinia/colada';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.use(PiniaColada);

app.mount('#app');

基本的なデータ取得の例

以下は、Pinia Colada を使ったデータ取得のシンプルな例です。

サンプルコード

<script setup lang="ts">
import { useQuery } from '@pinia/colada';

interface UserData {
  id: number;
  name: string;
  email: string;
}

const { state: user, asyncStatus } = useQuery<UserData>({
  key: () => ['user', 1], // キャッシュキーを設定
  query: async () => {
    // APIからデータを取得
    const res = await fetch('https://jsonplaceholder.typicode.com/users/1');
    if (!res.ok) throw new Error('データの取得に失敗しました');
    return res.json();
  },
  staleTime: 5 * 60 * 1000, // キャッシュの保持時間を設定
});
</script>

<template>
  <div>
    <!-- ローディング状態 -->
    <div v-if="asyncStatus === 'loading'">読み込み中...</div>

    <!-- エラー表示 -->
    <div v-else-if="asyncStatus === 'error'">エラーが発生しました</div>

    <!-- データ表示 -->
    <div v-else-if="user?.data">
      <h2>ユーザー情報:</h2>
      <p>名前: {{ user.data.name }}</p>
      <p>メール: {{ user.data.email }}</p>
      <p>ユーザーID: {{ user.data.id }}</p>
    </div>

    <!-- データがない場合 -->
    <div v-else>データが見つかりません</div>
  </div>
</template>

ポイント解説

  1. useQuery フック
    データフェッチのロジックを一箇所に集約します。結果はリアクティブな状態として扱えるため、Vueコンポーネント全体で簡単に利用できます。
  2. キャッシュ管理 (staleTime)
    取得したデータを指定した時間だけ保持し、不要なリクエストを防ぎます。この設定により、キャッシュの有効期限を自由に調整可能です。
  3. asyncStatus
    ローディング、エラー、データ取得済みといった状態を管理するための便利なフィールドです。状態ごとの分岐が明確になり、UIロジックの記述がシンプルになります。

まとめ

Pinia Colada を使うことで、非同期処理と状態管理がこれまで以上にシンプルになります。特に以下のような場面で力を発揮します:

  • コードの簡素化
    APIリクエストやローディング状態の管理を抽象化することで、記述量を削減できます。
  • 効率的なキャッシュ戦略
    データの再利用と重複リクエストの防止を組み合わせ、アプリケーションのパフォーマンスが向上します。
  • Vueとの高い親和性
    Piniaと統合されているため、Vueエコシステムの一部として自然に利用できます。
  • 開発体験の向上
    Vue DevToolsとの連携により、状態の可視化とデバッグが容易になり、非同期処理の挙動をより深く理解できます。

ぜひ Pinia Colada を導入して、Vue.js アプリケーションの非同期処理をスマートに管理しましょう!

公式ドキュメントも参考にしながら、プロジェクトで試してみてください

pinia-colada.esm.dev