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 は便利なグローバル変数ではなく、アプリケーションの状態を表す境界として扱いましょう。