Store 設計規則
13.2.1. Store 分割基準
Store は ドメイン単位 で分割します。1つの Store は1つのドメインのみを担当しなければなりません。
- 1 Store = 1 ドメイン原則を遵守します。
- ドメインはビジネスエンティティまたは機能領域を基準に定義します。
- 1つの Store が複数のドメインを管理することは禁止します。
| Store | 担当ドメイン | 含まれる状態の例 |
|---|---|---|
useUserStore | ユーザー認証/プロフィール | ログイン状態、ユーザー情報、トークン |
useProductStore | 商品 | 商品一覧、商品詳細、カテゴリ |
useCartStore | カート | カート項目、数量、合計 |
useNotificationStore | 通知 | 通知一覧、既読状態 |
useThemeStore | テーマ/UI 設定 | ダークモード、言語、レイアウト |
13.2.2. グローバル vs ローカル状態の判断
すべての状態を Store に配置しません。以下の判断基準に従って管理方式を決定しなければなりません。
| 判断基準 | 管理方式 | 例 |
|---|---|---|
| 複数のコンポーネントが共有する状態 | Pinia Store | 認証情報、テーマ設定、権限 |
| 単一コンポーネントでのみ使用する状態 | コンポーネントローカル ref | フォーム入力値、モーダル表示有無、トグル |
| サーバーから取得する非同期データ | Composable(useFetch など) | API レスポンスデータ、ページネーション |
| 複数ページにまたがる一時的な状態 | Pinia Store | マルチステップフォームウィザードの進行データ |
| URL で表現可能な状態 | Router(query/params) | 検索フィルター、ソート条件、ページ番号 |
- 判断が曖昧な場合は ローカル状態から開始 し、共有の必要性が確認された時点で Store に昇格させます。
13.2.3. Store 間の依存関係
Store 間の依存が必要な場合は action 内部で 別の Store を呼び出します。
- Store の最上位スコープで別の Store を呼び出しません。
- 循環依存は禁止します。 A -> B -> A 形式の参照が発生した場合、共通 Store を分離します。
- 依存方向は 上位ドメインから下位ドメインへ 単方向を維持します。
typescript
// src/stores/useCartStore.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { useUserStore } from './useUserStore'
import type { CartItem } from '@/types/cart'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const totalPrice = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
// action 内部で別の Store を呼び出します。
async function checkout() {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
throw new Error('ログインが必要です。')
}
await submitOrder(items.value, userStore.user!.id)
items.value = []
}
return { items, totalPrice, checkout }
})useUserStore()をcheckout()action 内部で呼び出すのが正しいパターンです。- Store 定義の最上位で
const userStore = useUserStore()を宣言することは禁止します。
13.2.4. Store ネーミング
Store のネーミングは以下の規則に従います。
- 関数名:
use{Domain}Storeパターンを使用します。(例:useAuthStore、useProductStore) defineStoreID: ドメイン名を小文字ケバブ表記で使用します。- ファイル名: 関数名と同一の
use{Domain}Store.tsで記述します。
| 関数名 | defineStore ID | ファイル名 |
|---|---|---|
useAuthStore | 'auth' | useAuthStore.ts |
useProductStore | 'product' | useProductStore.ts |
useOrderHistoryStore | 'order-history' | useOrderHistoryStore.ts |
typescript
// ファイル名: src/stores/useAuthStore.ts
// defineStore ID: 'auth'
// 関数名: useAuthStore
export const useAuthStore = defineStore('auth', () => {
// ...
})- ID はアプリ全体で 一意でなければなりません。重複 ID はランタイムエラーを発生させます。
- 複合ドメインの ID はケバブケースを使用します。(例:
'order-history'、'user-preference')