Pinia とは
Pinia は Vue 3 向けに設計された現代的な状態管理ライブラリです。Vuex の後継として開発され、Vue 公式が推奨するデファクトスタンダードとなっています。シンプルな API、優れた TypeScript サポート、軽量な体格が特徴です。
セットアップとインストール
Pinia をプロジェクトに追加するには、以下のいずれかのコマンドを実行します:
npm install pinia
# または
yarn add pinia
基本的な利用手順
1. ストアの定義
各機能をモジュール単位で明確に分離します。
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
value: 0,
label: 'default'
}),
getters: {
tripleValue: (state) => state.value * 3,
formattedValue: (state) => (prefix: string) => {`${prefix}: ${state.value}`}
},
actions: {
increase(delta: number = 1) {
this.value += delta
},
async refreshValue() {
const res = await fetch('/api/latest')
const data = await res.json()
this.value = data.current
}
}
})
2. アプリケーションへの統合
Vue アプリケーションの初期化時に Pinia をプラグインとして登録します。
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
3. コンポーネントでの利用
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
const onAdd = () => store.increase(2)
const onUpdateLabel = () => {
store.$patch({ label: 'updated' })
}
</script>
<template>
<div class="counter">
<p>値:{{ store.value }}(3倍:{{ store.tripleValue }})</p>
<p>ラベル:{{ store.formattedValue('Number') }}</p>
<button @click="onAdd">+2 増加</button>
<button @click="onUpdateLabel">ラベル更新</button>
</div>
</template>
主要な概念
state:基本的な状态保持
初期状態を関数で返す形式で定義します。配列やオブジェクトなども реактивに扱えます。
$patch メソッドで複数の状態を一括更新可能です:
store.$patch({
value: 10,
label: ' Immediate update'
})
getters:依存計算ロジック
従来の computed に相当します。返り値として関数を持つことも可能で、パラメータを受け取る柔軟な計算が実現できます:
getters: {
withThreshold: (state) => (threshold: number) => state.value >= threshold
}
actions:状態変更ロジック
>
非同期処理(例:API 呼び出し、ロギング、デバイス操作など)を含めることも自然に記述可能です:
actions: {
async loadConfig() {
try {
const cfg = await api.getConfig()
this.$patch({ config: cfg })
} catch (e) {
console.error('設定読み込み失敗:', e)
}
}
}
高度な活用方法
プラグイン:拡張機能の追加
Pinia のグローバルな挙動をカスタマイズするための仕組みです。たとえば、状態変更時のログ記録や永続化ミドルウェアの統合が可能です:
import { persistancePlugin } from '@/plugins/persistence'
const pinia = createPinia()
pinia.use(persistancePlugin)
// プラグイン実装例
export const persistancePlugin = (context) => {
const { store } = context
store.$subscribe((event, state) => {
localStorage.setItem('pinia-' + store.$id, JSON.stringify(state))
})
}
SSR での利用
Pinia は Vue 3 の SSR 設定と自然に連携します。クライアントとサーバー間での状態同期を実現するには、`createPinia()` をリクエストごとに生成し、グローバルなインスタンスを避けることが推奨されます。
// server-entry.ts
import { createSSRApp } from 'vue'
import { createPinia } from 'pinia'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
return { app, pinia }
}
Vuex との比較 : Pinia の利点
- 型推論が自然に機能し、型安全な書きやすさ向上
- エントリーポイントが単一の関数で統一(`defineStore`)
- `mapState` / `mapActions` といった補助関数が不要(コンポジション API 風)
- ネスト構造を持つモジュールが明示的に不要(モジュール分割无需)
動的状態の追加とその制約
動的状態追加の方法
通常、Pinia では初期状態で定義された属性のみが型として保証されます。ただし、必要に応じて以下のような手段で動的追加が可能です:
① $patch を使った安全な追加
const store = useAppStore()
store.$patch({ sessionData: { token: 'xxx', expires: Date.now() } })
② $state による全体置換
store.$state = {
...store.$state,
sessionData: { token: 'xxx', expires: Date.now() }
}
③ 直接代入(非推奨)
phản응性が失われる可能性があるため、避けるべきです。
実用的な動的状態管理パターン
型安全かつ柔軟性を担保したい場合、以下のような専用オブジェクトフィールドを用意し、操作専用メソッドを提供するのが望ましいです:
interface DynamicStateMap {
[key: string]: any
}
export const useDynamicStore = defineStore('dynamic', {
state: () => ({
dynamicEntries: {} as DynamicStateMap
}),
actions: {
registerEntry(key: string, value: any) {
this.dynamicEntries = {
...this.dynamicEntries,
[key]: value
}
},
removeEntry(key: string) {
const { [key]: removed, ...rest } = this.dynamicEntries
this.dynamicEntries = rest
}
}
})