Pinia

Vue 3 アプリケーションで共有 state を整理する Pinia の Store 設計を学ぶ。

Pinia は Vue の状態管理ライブラリです。複数の画面や離れたコンポーネントで共有する state を Store にまとめ、取得・更新・派生値を一貫した形で扱えます。

ただし、すべての state を Store に移す必要はありません。モーダルの開閉や一つの入力欄の値など、コンポーネント内だけで完結する状態はローカルの ref や reactive に置く方が分かりやすいことが多いです。

Store が解く問題

親子が近いコンポーネントなら、props と emit による一方向のデータフローが基本です。しかし、認証済みユーザー、カート、通知、アプリ共通の表示設定などを多くの場所で使う場合、props を何層も渡すのは負担になります。

Store は共有 state の責務を一か所に集めます。画面は Store の state を読むか action を呼ぶだけになり、どこで値が変更されるかを追いやすくなります。

Setup Store の基本

Vue 3 の Composition API と同じ書き方で Store を定義できます。ref は state、computed は getter、通常の関数は action として公開されます。

// stores/cart.ts
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const totalCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0),
  )

  function addItem(item: CartItem) {
    items.value.push(item)
  }

  return { items, totalCount, addItem }
})

最初の引数である 'cart' はアプリ内で一意の Store ID です。Devtools との連携にも使われるため、役割が分かる安定した名前を付けます。

コンポーネントで使う

コンポーネント内では useCartStore() で Store を取得します。Store はリアクティブなオブジェクトなので、state や getter を直接参照できます。

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()
const { items, totalCount } = storeToRefs(cart)
</script>

<template>
  <p>{{ totalCount }} 点</p>
  <button @click="cart.addItem(product)">追加</button>
</template>

state や getter を分割代入するときは storeToRefs() を使います。単純な分割代入ではリアクティブなつながりが失われます。一方、action は Store に束縛されているため、必要なら直接分割代入できます。

Store をどう分けるか

「画面ごと」ではなく、データの責務で分けます。たとえば auth、cart、notifications のように、独立して理解・テストできる単位が目安です。

  • state: アプリが記憶する事実
  • getter: state から導出できる表示用の値
  • action: state の変更、通信、複数操作の調整

巨大な万能 Store は依存関係を隠します。Store 同士が強く参照し合い始めたら、責務や API 境界を見直します。

Option Store と選び方

Pinia には state、getters、actions を持つ Options Store もあります。Options API に慣れたチームでは読みやすい選択です。Setup Store は composable を利用しやすく柔軟ですが、返さなかった state は Pinia が追跡できず、SSR や Devtools でも扱えません。

どちらを選んでも、Store の公開 API を小さくし、UI から内部構造を直接変更しない設計が重要です。

導入判断と注意点

Pinia を導入するのは、共有 state があり、props や composable だけでは責務が散らばるときです。サーバーのデータを無条件に global state へ複製する前に、キャッシュ、更新、エラー、SSR の方針も決めます。Store は便利なグローバル変数ではなく、アプリケーションの状態を表す境界として扱いましょう。

Sources