Pinia(読み方:ピーニャ)は、Vue.jsの公式の状態管理ライブラリです。複数のコンポーネントから読み書きするデータを「ストア」にまとめる仕組みで、Vue公式ドキュメントはVuexに代えてPiniaを推奨しています。最新版は2026年10月14日公開の4.1.0です。4系では @vue/devtools-api を一緒にインストールしないと読み込みエラーになり、4.1.0ではストア一覧を取る getStores() が加わりました。本記事は、Vuexとの違い、版ごとの変更点と3からの移行手順、ストアの書き方、つまずきやすいエラーを、2026年10月28日時点のnpmレジストリと公式ドキュメント、ローカル環境(Pinia 4.1.0・Vue 3.5.44・@vue/devtools-api 8.2.1・Node.js 26.5.0・Vitest 5.0.5・@pinia/testing 2.0.2)で動かした結果をもとに解説します。
まとめ:Piniaの要点
- 正体:Vueのコアチームが保守する公式の状態管理ライブラリ。VuexはVue公式ドキュメントで「maintenance mode」とされ、新規開発にはPiniaが推奨されている。
- 読み方:「ピーニャ」。スペイン語でパイナップルを意味する piña に由来する。
- Vuexとの違い:mutationsが無く、stateはactionsか直接代入で書き換える。モジュールの入れ子の代わりに、小さなストアを複数並べる。
- 最新版:4.1.0。4系はESMのみの配布で、
npm install pinia @vue/devtools-apiの2パッケージ導入が公式手順。ストアの書き方は3系から変わっていない。 - つまずきやすい点:ストアを分割代入すると値が追従しないので
storeToRefs()を使う。Setup Storeには$reset()が無いので自分で定義する。テスト用のcreateTestingPinia()は既定でactionsを実行しない。
Piniaの位置づけ:Vue公式の状態管理ライブラリ
Piniaは、Vuexの次期版(Vuex 5)の構想を試す実験として始まりました。Piniaの公式ドキュメントは、Vuex 5で実現したかったことの大半を既にPiniaが実装していると分かったため、代わりにPiniaを新しい推奨にしたと経緯を説明しています。仕様はVueのほかのコアライブラリと同じRFCの手続きで決まります。
読み方「ピーニャ」と名前の由来
公式ドキュメントには「Pinia (pronounced /piːnjʌ/, like peenya in English)」とあり、日本語では「ピーニャ」が近い発音です。「ピニア」と読まれることもありますが、公式の発音記号に沿うなら「ピーニャ」です。名前はnpmで使える名前のうち piña(スペイン語でパイナップル)に最も近いものとして選ばれ、小さな花が集まって1つの実になるパイナップルで、個別のストアが最後につながる設計を表しています。
ストアに置くデータと、refやコンポーザブルで足りるデータ
公式が挙げるストアの用途は、ナビゲーションバーに出すユーザー情報のようにアプリの複数箇所で使うデータと、複数ページにまたがる入力フォームのようにページ遷移後も保持したいデータです。ある画面の要素の表示・非表示のように1つのコンポーネントで完結するデータは、ストアに入れず ref で持ちます。
コンポーネントの外、すなわちモジュールスコープで定義してexportした ref をimportすれば、SPAでは状態を共有できます。ただしSSRでは、この方法だとサーバー上でリクエストをまたいで状態が共有されてしまい、公式はセキュリティ上の問題になると注意しています。それでもPiniaを使う理由は、Vue Devtoolsでの状態の確認と巻き戻し、SSRでのサーバーからクライアントへの状態の受け渡し、プラグインによる永続化、テストでの差し替えがそろって使えることです。逆に、処理を共有したいだけで状態を持たないなら、use で始まるコンポーザブル関数で足ります。
PiniaとVuexの違い:mutations廃止と保守モード
Vuexは3系がVue 2用、4系がVue 3用です。npmの最終リリースは2022年10月14日の4.1.0です。VuexのREADMEは「Vuex 3 and 4 will still be maintained. However, it’s unlikely to add new functionalities to it.」と書き、Vue公式ドキュメントの状態管理のページも「It is recommended to use Pinia for new applications.」としています。
| 項目 | Vuex 4 | Pinia 4 |
|---|---|---|
| 状態の変更 | mutationsをcommit | actionsまたは直接代入 |
| ストアの構成 | 1ストア+modules | 複数ストアを並列 |
| 名前空間 | namespaced: true | ストアIDで区別 |
| actionの呼び出し | dispatch(‘cart/add’) | store.add() |
| TypeScript | 型の補助定義が必要 | 定義から型推論 |
| 最新リリース | 4.1.0(2022-10-14) | 4.1.0(2026-10-14) |
mutationsはもともとVue Devtoolsで変更を追跡するための仕組みでしたが、Piniaでは直接代入もDevtoolsに記録されるため不要になりました。ストア取得関数をimportし、取得したストアのメソッドとしてactionを呼ぶため、エディタの補完と型チェックが効きます。
Piniaのバージョン:4.1までの変更点と3からの移行
Piniaのメジャーバージョンは、新機能よりも対応環境の整理で上がってきました。3.0も4.0も、リリースノートは破壊的変更を技術的なものに限ると説明しています。
| 版 | 公開日 | 主な変更 |
|---|---|---|
| 2.0.0 | 2021-10-27 | Vue 2とVue 3の両対応 |
| 3.0.0 | 2025-02-11 | Vue 2対応と非推奨APIを削除 |
| 4.0.0 | 2026-07-14 | ESMのみ、devtools-api v8が必須 |
| 4.0.3 | 2026-08-12 | SSRのSet・Map復元を修正 |
| 4.1.0 | 2026-10-14 | getStores()、破棄の警告を追加 |
3.0では、Vue 2対応に加えて PiniaStorePlugin 型と defineStore({ id: 'id' }) の書き方が削除されました。4.0ではエラーと開発時の警告が、Nosticsという診断ライブラリで作り直されています。警告に PINIA_R1008 のようなコードと修正方法、ドキュメントのURLが付くのはこのためです。
4.1.0で増えたgetStores()と破棄の警告
4.1.0のリリースノートに載った新機能は2つです。1つ目の getStores() は、Piniaインスタンスで使用中のストアを配列で返します。これまでは内部プロパティの pinia._s を直接たどるしかありませんでした。新しい配列が返るので、ループ中にストアを破棄しても安全です。
import { getStores } from 'pinia'
// 登録済みのpiniaを使用。破棄後に各ストアを再取得する前提の例
// 認証以外のストアを破棄する。既存の参照は自動では更新されない
for (const store of getStores(pinia)) {
if (store.$id === 'auth') continue
store.$dispose()
delete pinia.state.value[store.$id] // $dispose()だけではstateが残る
}
実測では、cart・auth・b・u の4つが返り、ループ内で b を破棄した後は3つになりました。注意したいのは、$dispose() はストアのeffect scopeを停止して登録を解除しますが、stateは消さない点です。n を5にしたストアを $dispose() だけして作り直すと、n は5のままでした。型定義のコメントにあるとおり delete pinia.state.value[store.$id] も実行すると、作り直したストアは初期値の0に戻りました。
2つ目は、置き換え済みの古いストアに対する $dispose() の警告です。$dispose() した後で同じIDのストアをもう一度 useXxxStore() で作り、手元に残っていた古い参照で再び $dispose() を呼ぶと、4.1.0では [PINIA_R1008] "$dispose()" was called on an old instance of store "a" that has already been replaced by a new one. This also removes the new store. と警告されます。メッセージのとおり新しいストアも登録から外れ、実測でも getStores() に新しいストアは残っていませんでした。破棄済みのストアの参照は再利用せず、破棄後にストアが必要になったら useXxxStore() を呼び直してください。
Pinia 3から4へ上げる手順
ストアの定義やコンポーネント側のコードは変える必要がありません。作業は依存パッケージの更新が中心です。
npm install pinia@4 @vue/devtools-apiを実行する。@vue/devtools-apiは4系の必須のpeer dependencyで、公式のインストール手順も「Since v4, you must also install@vue/devtools-api」と明記している。- TypeScriptを使っているなら5.6以上か確かめる。peer dependencyの下限は、Pinia 3.0.4が
>=4.5.0、4.1.0が>=5.6.0に上がった。Vueは^3.5.11のまま。 - 関連パッケージを揃えて上げる。
@pinia/testingは2.0.2がpinia >=4.1.0、@pinia/nuxtは1.0.3がpinia ^4.1.0を要求する(Pinia 3系向けの@pinia/testing1.xは、1.0.0のpinia >=3.0.0から1.0.3の>=3.0.4まで、版ごとに下限が上がっている)。 - CommonJSでPiniaを読み込んでいる設定(古いJestの設定など)があれば、テストを一度通して確かめる。
--legacy-peer-deps 付きでPinia 4.1.0とVue 3.5.44だけを入れて import { createPinia } from 'pinia' を実行すると、Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@vue/devtools-api' で起動に失敗しました。peer dependencyを自動で入れない設定のプロジェクトでは、package.jsonに明示しておきます。ESMのみになった点は、@vue/devtools-api が入っていればNode.js 26.5.0の require('pinia') でも読み込めました(Node.jsのrequire(esm)機能による)。
Vue 2のプロジェクトは4系に上げられません。Vue 2.7で使えるのはPinia 2.3系まで、Vue 2.6はさらに古い2.2系以前です。Vue公式ドキュメントの状態管理のページには今も「works with both Vue 2 and Vue 3」とありますが、これはPinia 2系までの話です。
PiniaのインストールとVueアプリへの登録
ViteやVue公式の create-vue で作ったVue 3プロジェクトなら、次のコマンドで導入します。create-vue の質問でPiniaを選ぶと、ここまでの設定が入ったプロジェクトが生成されます。yarn・pnpm・bun・denoでもパッケージ名は同じ2つです。
npm install pinia @vue/devtools-api
次に、ルートのPiniaインスタンスを作り、アプリにプラグインとして登録します。
// src/main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
ストアを定義する:Option StoreとSetup Store
ストアは defineStore() で定義します。第1引数のIDはアプリ内で一意の文字列で、Devtoolsの表示名やSSRでのシリアライズのキーになります。書き方は2種類です。
Option Store:Options APIに近い書き方
state・getters・actions を持つオブジェクトで定義します。公式はこの3つを、コンポーネントの data・computed・methods に相当すると説明しています。
// src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
Setup Store:Composition APIと同じ書き方
関数の中で ref(state)・computed(getters)・関数(actions)を作り、returnします。ストアの中で watch や useRoute() などのコンポーザブルを使えるのが利点です。
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const total = computed(() => items.value.reduce((sum, i) => sum + i.price, 0))
function add(item) {
items.value.push(item)
}
return { items, total, add }
})
公式は、stateのプロパティをすべてreturnするよう強調しています。returnしない、あるいは readonly で包んだstateは、SSR・Devtools・プラグインから扱えなくなります。
Option StoreとSetup Storeの選び方
公式の立場は「慣れている方を選べばよい」です。判断の目安は、コンポーネントをどちらのAPIで書いているかです。
| 状況 | 向く書き方 |
|---|---|
| Vuexから移行する | Option Store |
| $reset()をよく使う | Option Store |
| script setupで書いている | Setup Store |
| ストア内でwatchや他のコンポーザブルを使う | Setup Store |
両方を同じアプリで混在させても動きますが、新しく作るストアはどちらかに揃えた方が読みやすくなります。Composition APIの書き方はComposition APIとは?書き方とOptions APIとの違い・移行判断を解説【2026年8月版】で解説しています。
gettersとactionsを書く:引数付きgetter・非同期処理・ストア間の呼び出し
引数を受け取るgetterと、ほかのgetterの参照
getterは引数を取れないので、関数を返すgetterにします。Option Storeでほかのgetterを使うときは、アロー関数ではなく通常の関数で書いて this から参照します。
export const useUserStore = defineStore('users', {
state: () => ({ users: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] }),
getters: {
byId: (state) => (id) => state.users.find((u) => u.id === id),
count: (state) => state.users.length,
label() {
return `${this.count}人`
},
},
})
実測では byId(2).name が b、label が 2人 を返しました。ただし公式のgettersのページは、関数を返すgetterはキャッシュされなくなると注意しています。大きな配列を何度も検索するなら、IDをキーにしたオブジェクトをgetterで作っておき、そこから引く方が速くなります。
非同期actionのloading・error管理
actionは async にでき、APIの呼び出しをそのまま書けます。画面に読み込み中と失敗を出すには、その状態もstateに持ち、finally で読み込み中を解除します。
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const loading = ref(false)
const error = ref(null)
async function load() {
const auth = useAuthStore() // 別のストアはaction内で呼ぶ
loading.value = true
error.value = null
try {
const res = await fetch('/api/cart', {
headers: { Authorization: `Bearer ${auth.token}` },
})
if (!res.ok) throw new Error(String(res.status))
items.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
}
return { items, loading, error, load }
})
APIの代わりに10ミリ秒待つ処理で試すと、await の途中では loading が true、成功後と失敗後はどちらも false に戻り、失敗時は error に例外のメッセージが入りました。
ストア間参照の取得場所と循環参照の制約
上の例の useAuthStore() はaction内で呼び出していますが、Setup Storeでは関数本体の直下で別のストアを取得することもできます。実測でも、認証ストアに入れたトークンをカートストアのaction内で読めました。避けるべきなのは、2つのSetup Storeが初期化中(関数本体の直下)に互いのstateを読み合う構成と、getterやactionが互いを無限に呼び合う構成です。前者は初期化の順序によって未定義の値を読むことになります。
コンポーネントで使う:分割代入とstoreToRefs
ストアは定義した関数(useCounterStore())を呼ぶと取得できます。同じPiniaインスタンス内では同じIDのストアが返り、実測でも useCounterStore() === store は true でした。
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const store = useCounterStore()
const { count, double } = storeToRefs(store) // stateとgettersはref化して取り出す
const { increment } = store // actionsはそのまま取り出せる
</script>
<template>
<button @click="increment">{{ count }}(2倍: {{ double }})</button>
</template>
ストアは reactive で包まれたオブジェクトなので、const { count } = store と分割代入すると、その時点の値がコピーされるだけです。increment() を1回呼んでから分割代入し、もう1回呼ぶと、分割代入した count は1のまま、ストアは2、storeToRefs() で取り出した値は2でした。actionsはストアに束縛されているので、分割代入して呼んでも動きます。
SSRでは、setup() の外(ナビゲーションガードなど)でストアを取得するときに、リクエストごとのPiniaインスタンスを useStore(pinia) のように渡します。setup() の中では渡す必要はありません。
stateを更新・リセット・監視する:$patch・$reset・$subscribe・$onAction
stateは store.count++ のように直接書き換えられます。複数の値をまとめて変えるときは $patch を使い、配列への追加のようにオブジェクトで表しにくい変更は関数形式で渡します。
store.$patch({ count: 10 })
const cart = useCartStore()
cart.$patch((state) => {
state.items.push({ price: 100 })
})
Setup Storeの$reset定義
Option Storeでは store.$reset() でstateを初期値に戻せます。Setup Storeで同じ呼び出しをすると、Pinia 4.1.0では「Store “cart” is built using the setup syntax and does not implement $reset().」というエラーになりました。Setup Storeでは自分で $reset を定義してreturnします。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
$subscribeの通知回数
$subscribe はstateの変更ごとにコールバックを呼び、変更の種類を 'direct'(直接代入)・'patch object'・'patch function' で渡します。既定では、同じtick内の直接代入はまとめて1回の通知になります。store.count++ を2回続けて nextTick() を待つと通知は1回で、第2引数に { flush: 'sync' } を渡すと2回でした。通知回数を変更回数として数えることはできません。変更のたびにlocalStorageへ保存するといった、最新のstateを書き出す用途に向いています。
$onActionによるactionの終了・例外の記録
stateではなくactionの呼び出しを見たいときは $onAction を使います。コールバックはaction名と引数を受け取り、after() に成功時、onError() に失敗時の処理を登録できます。非同期actionなら、Promiseが解決した後に after() が呼ばれます。
cart.$onAction(({ name, args, after, onError }) => {
const start = Date.now()
after(() => console.log(name, args, `${Date.now() - start}ms`))
onError((e) => console.error(name, e.message))
})
実測では、失敗の有無を引数で切り替える版の load(false) で、完了後に after が呼ばれ、action名 load と引数 [false] を受け取りました。例外を投げる同期actionでは onError が呼ばれました。onError に届くのは、例外を外へ投げるかPromiseをrejectするactionだけです。前章の load() は catch の中で例外を処理して正常に終わるため、APIが失敗しても after が呼ばれ、onError は呼ばれません。エラー監視へ送りたいなら、catch の中で error に入れた後に例外を投げ直します。
NuxtでPiniaを使う設定
Nuxtでは公式モジュール @pinia/nuxt(最新1.0.3)を使います。公式のNuxtのページはNuxt 3と4への対応を明記しており、1.0.2のリリースノートではNuxt 5への対応も宣言されました。導入は次のコマンドで、nuxt.config の modules に '@pinia/nuxt' が追加されます。
npx nuxi@latest module add pinia
モジュールは defineStore()・storeToRefs()・usePinia()・acceptHMRUpdate() を自動インポートし、stores ディレクトリ(Nuxt 4では app/stores)のストアも自動で読み込みます。入れ子のディレクトリは対象外なので、使う場合は pinia.storesDirs で指定します。1.0.3では、Nuxtのレイヤーを使う構成で既定の stores ディレクトリがレイヤーごとに解決されるよう修正されました。
ページでデータを取得するactionは、await callOnce('user', () => store.fetchUser()) のように callOnce() で包むと、サーバーで取得済みのデータをクライアントで取り直しません。Nuxt 4のディレクトリ構成はNuxt 4とは?最新バージョンは4.6|確認方法・Nuxt 3との違い・導入手順【2026年10月】で扱っています。
VuexからPiniaへ移行する手順
VuexとPiniaは同じプロジェクトに同時にインストールできるので、画面や機能の単位で段階的に移せます。公式の移行ガイドが示す対応関係は次の4点です。
- モジュール:Vuexのモジュール1つをPiniaのストア1つにする。入れ子のモジュールもそれぞれ独立したストアにする。
- 名前空間:ストアIDに旧名前空間に対応する名前を付ける(公式の例では
nested/module2を'nestedModule2'にしている)。フォルダ名もstoreではなく複数形のstoresにする。 - mutations:actionsに統合する。単純な代入だけのmutationは削除し、呼び出し側で直接代入するか
$patchを使う。 - rootState・rootGetters:他のストアをimportして呼ぶ形に書き換える。
コンポーネントで mapState・mapActions を使っていた箇所は、Piniaにも同名のヘルパー(mapState()・mapActions()・mapStores())があるので、Options APIのままでも書き換えられます。
Piniaのテスト:createTestingPiniaの既定動作
ストア単体のテストは、各テストの前に setActivePinia(createPinia()) で新しいPiniaを作れば十分です。コンポーネントのテストでは、@pinia/testing の createTestingPinia() を使います。公式のテストのページにあるとおり、既定でactionsがすべてスタブ(呼び出しを記録するだけの空関数)に置き換わる点を見落としがちです。Vitest 5.0.5で確かめると、increment() を呼んでも呼び出し回数だけが1になり、stateは0のままでした。
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { vi } from 'vitest'
setActivePinia(createTestingPinia({
createSpy: vi.fn, // globals: true でなければ必須
stubActions: false, // actionsを実際に実行する
initialState: { counter: { count: 20 } }, // ストアIDごとに初期stateを指定
}))
もう1つの落とし穴は、Setup Storeの中でaction同士を呼ぶ場合です。incTwice() の中から inc() を2回呼ぶストアで stubActions: false にして incTwice() を実行すると、stateは2になりましたが、inc のスパイの呼び出し回数は0でした。ストア内部の呼び出しはスパイを経由しないためで、公式ドキュメントにも同じ注意があります。内部呼び出しは、呼び出し回数ではなくstateの結果で確かめます。Vitestの書き方はVitestの使い方と書き方:読み方・設定からReact Testing Libraryのコンポーネントテストまで【Vitest 5対応】、4系からの移行はVitest 5移行ガイド:4からの破壊的変更・新機能と落ちるテストの直し方【5.0.5で実測】を参照してください。
Piniaの実行時エラーと修正方法
| 症状・メッセージ | 原因 | 直し方 |
|---|---|---|
| getActivePinia() was called but there was no active Pinia | app.use(pinia)より前にストアを呼んだ | 登録順を直す・テストはsetActivePinia |
| Cannot find package ‘@vue/devtools-api’ | 4系の必須peerが無い | @vue/devtools-apiを追加 |
| does not implement $reset() | Setup Storeで$resetを呼んだ | $resetを定義してreturn |
| 分割代入した値が画面で変わらない | 値のコピーになっている | storeToRefs()で取り出す |
| Devtoolsや永続化にstateが出ない | Setup Storeでreturnしていない | stateをすべてreturn |
| PINIA_R1008 | 古いストアの参照で$dispose() | useXxxStore()を呼び直す |
| テストでactionが動かない | createTestingPiniaの既定スタブ | stubActions: false |
先頭の getActivePinia() のエラーは、ストアを使うファイルのトップレベル(main.js でimportされた瞬間に実行される場所)で useXxxStore() を呼んだときによく出ます。公式のコンポーネント外での使い方のページのとおり、関数やナビゲーションガードの中へ呼び出しを移し、その関数がapp.use(pinia)による登録後に実行されるようにします。
Piniaを使わない方がよい場面
Piniaは導入が軽いぶん、何でもストアに入れてしまいがちです。次の場合は使いません。
- 親子2〜3階層で完結するデータ:propsとemit、または
provide/injectで足りる。ストアに入れると、どの画面が値を変えたかを追いにくくなる。 - 呼び出し元ごとに別の状態を持たせたい処理:ストアは同じPiniaインスタンス内でIDごとに1つしか作られない。フォームの部品のように使う場所ごとに状態を分けたいなら、コンポーザブルにする。
- サーバーから取ってきたデータのキャッシュ:Pinia本体だけで作ると、再取得・キャッシュの破棄・読み込み中の状態を手で管理することになる。前章の非同期actionの例は1つのAPIなら十分だが、画面ごとにAPIが増えるなら、Piniaの作者Eduardo San Martin Morote氏が開発する Pinia Colada(
@pinia/colada1.4.8、Pinia 4系にも対応)に任せる方が短く書ける。 - Vue 2.6以前のプロジェクト:使えるのはPinia 2.2系以前と
@vue/composition-apiの組み合わせで、3系以降はVue 2に対応しない。Vue 3への移行と合わせて検討する。
PiniaはVueのリアクティブシステムの上に作られているため、Reactでは使えません。React向けの状態管理はZustandとは?Reactの状態管理を最小コードで実現する使い方を徹底解説【v5対応】で比べています。
よくある質問
Piniaの読み方は?
「ピーニャ」です。公式ドキュメントが発音を /piːnjʌ/(英語の peenya に近い)と示しており、スペイン語でパイナップルを意味する piña が由来です。
VuexとPiniaはどちらを使うべきですか?
新規開発ならPiniaです。Vue公式ドキュメントはVuexを保守モードとし、新しいアプリにはPiniaを推奨しています。既存のVuexプロジェクトは動き続けますが、2022年10月の4.1.0以降リリースが無いため、段階的な移行を計画してください。
Piniaの最新バージョンは?
2026年10月28日時点で4.1.0(2026年10月14日公開)です。最新はnpmで npm view pinia version を実行すると確認できます。
PiniaはReactでも使えますか?
使えません。PiniaはVueの ref・reactive に依存しており、peer dependencyにもVueが入っています。ReactならZustandやRedux Toolkitが相当します。
Piniaのstateをリロード後も残せますか?
Pinia本体には永続化の機能はありません。pinia-plugin-persistedstate(4.7.1、peer dependencyは pinia >=3.0.0)を pinia.use() で登録し、ストアに persist: true を付けると、既定でlocalStorageに保存・復元されます。