12 人が閲覧(直近 30 日) Vue.js

Piniaとは?読み方・使い方とVuexとの違い【Pinia 4対応】

Piniaとは?読み方・使い方とVuexとの違い【Pinia 4対応】

Pinia(読み方:ピーニャ)は、Vue.jsの公式の状態管理ライブラリです。複数のコンポーネントから読み書きするデータを「ストア」にまとめ、Vuexの後継としてVue公式ドキュメントで推奨されています。最新版は2026年8月12日公開の4.0.3で、4系からは @vue/devtools-api を一緒にインストールしないと読み込みエラーになります。本記事では、Vuexとの違い、4系の変更点、ストアの書き方と使い方の注意点を、2026年9月28日時点のnpmレジストリと公式ドキュメント(v4.0.3のソース)、ローカル環境(Pinia 4.0.3・Vue 3.5.43・Node.js 26.5.0・Vitest 5.0.2)で動かした結果をもとに解説します。

まとめ:Piniaの要点

  • 正体:Vueのコアチームが保守する公式の状態管理ライブラリ。Vue公式ドキュメントはVuexを「maintenance mode」とし、新規開発にはPiniaを推奨している。
  • 読み方:「ピーニャ」。スペイン語でパイナップルを意味する piña に由来する。
  • Vuexとの違い:mutationsが無く、stateをactionsや直接代入で書き換える。モジュールの入れ子や名前空間の代わりに、小さなストアを複数並べる。
  • 最新版:4.0.3。4系はESMのみの配布になり、npm install pinia @vue/devtools-api の2パッケージ導入が公式手順。Vue 2で使えるのは2系まで。
  • 使い方の注意:ストアを分割代入するとリアクティブ性が切れるので storeToRefs() を使う。Setup Storeには $reset() が無いので自分で定義する。

Piniaの位置づけ:Vue公式の状態管理ライブラリ

Piniaは、Vuexの次期版(Vuex 5)の構想を試す実験として始まりました。公式ドキュメントは「Piniaが既にVuex 5で実現したかったことの大半を実装していると分かり、代わりにPiniaを新しい推奨にした」と経緯を説明しています。現在はVueエコシステムの他のコアライブラリと同じRFCの手続きで仕様が決まり、APIは安定段階に入っています。

読み方「ピーニャ」と名前の由来

公式ドキュメントには「Pinia (pronounced /piːnjʌ/, like “peenya” in English)」とあり、日本語では「ピーニャ」が近い発音です。piña(スペイン語でパイナップル)に最も近く、npmで使えるパッケージ名として選ばれました。パイナップルは小さな花が集まって1つの実になることから、「個別に生まれたストアが最後にはつながる」というストアの設計を表しています。ロゴがパイナップルなのもこのためです。

ストアに置くデータと置かないデータ

公式が挙げるストアの用途は、ナビゲーションバーに出すユーザー情報のようにアプリの複数箇所で使うデータと、複数ページにまたがる入力フォームのようにページ遷移後も保持したいデータです。反対に、ある画面の要素の表示・非表示のようにコンポーネント内で完結するデータは、ストアに入れず ref で持つよう勧めています。処理を共有したいだけなら、ストアではなくコンポーザブル(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公式ドキュメントの状態管理のページも「Vuex is now in maintenance mode」「It is recommended to use Pinia for new applications.」としています。

項目 Vuex 4 Pinia 4
状態の変更 mutationsをcommit actionsまたは直接代入
ストアの構成 1ストア+modules 複数ストアを並列
名前空間 namespaced: true ストアIDがそのまま役割
呼び出し 文字列(’cart/add’) 関数を直接import
TypeScript 型の補助定義が必要 定義から型推論
最新リリース 4.1.0(2022-10-14) 4.0.3(2026-08-12)

mutationsはもともとVue Devtoolsで変更を追跡するための仕組みでしたが、Piniaでは直接代入もDevtoolsに記録されるため不要になりました。文字列でactionを呼ぶ代わりに関数をimportするので、エディタの補完と型チェックが効きます。ストア同士の相互利用は可能ですが、Setup Storeの初期化中に互いのstateを直接読み合う構成や、getters・actionsが無限に呼び合う構成は避ける必要があります。

Piniaのバージョン:4系の変更点と2系・3系との違い

Piniaのメジャーバージョンは、新機能よりも対応環境の整理で上がってきました。3.0も4.0も、リリースノートは「新機能は無い」「技術的な破壊的変更のみ」と説明しています。各版の変更内容はPiniaのCHANGELOGで確認できます。

版 公開日 主な変更
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復元などを修正

3.0では、Vue 2対応に加えて PiniaStorePlugin 型と defineStore({ id: 'id' }) の書き方が削除されました。4.0では配布がESMのみになり、エラーと開発時の警告がNosticsという診断ライブラリで作り直されています。4.0.3のpackage.jsonに記載されたpeer dependency(TypeScriptは任意依存で、使用する場合の最低バージョン)は、vue ^3.5.11・typescript >=5.6.0・@vue/devtools-api ^8.1.5 です。

Pinia 4へ上げるときの確認点

最も影響が大きいのは @vue/devtools-api です。公式のインストール手順は「Since v4, you must also install @vue/devtools-api」と明記しています。npm 7以降は通常peer dependencyを自動で入れますが、--legacy-peer-deps 付きでPinia 4.0.3とVue 3.5.43だけを入れて import { createPinia } from 'pinia' を実行すると、Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@vue/devtools-api' imported from .../pinia/dist/pinia.js で起動に失敗しました。peerを自動で入れない設定のプロジェクトでは、package.jsonに明示しておくのが安全です。

ESMのみになった点は、Node.js 26.5.0では require('pinia') でも読み込めました(Node.jsのrequire(esm)機能による)。CommonJS前提の古いテスト設定やビルドツールを使っているなら、上げる前にテストを一度通して確かめてください。なお、Vue公式ドキュメントの状態管理のページには「works with both Vue 2 and Vue 3」と書かれていますが、これはPinia 2系までの話です。Vue 2.7のプロジェクトで使えるのはPinia 2.3系までです。

Piniaのインストールと初期設定

ViteやVue公式の create-vue で作ったVue 3プロジェクトなら、次のコマンドと初期設定で導入できます。create-vue の質問でPiniaを選ぶと、ここまでの設定が最初から入ったプロジェクトが生成されます。

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')

yarn・pnpm・bun・denoでも、パッケージ名は同じ2つです。Nuxtを使う場合は、後述するモジュールで導入します。

ストアの定義:Option StoreとSetup Storeの書き分け

ストアは defineStore() で定義し、第1引数のID(アプリ内で一意の文字列)がDevtoolsの表示名やSSRのシリアライズのキーになります。書き方は2種類あります。

Option Store:Options APIに近い書き方

state・getters・actions の3つを持つオブジェクトで定義します。公式はこの3つを、コンポーネントの data・computed・methods に相当すると説明しています。

// src/stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, items: [] }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

Setup Store:Composition APIと同じ書き方

関数の中で ref(state)・computed(getters)・関数(actions)を作り、返します。ストアの中で watch を使ったり、他のコンポーザブルを呼んだりできるのが利点です。

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のプロパティはすべて返す必要がある」と強調しています。返さない、あるいは readonly で包んだstateは、SSR・Devtools・プラグインが扱えなくなります。

Option StoreとSetup Storeの選択基準

公式の立場は「慣れている方を選べばよい」で、Option Storeは扱いやすく、Setup Storeは柔軟です。判断の目安は、コンポーネントをどちらのAPIで書いているかです。<script setup> で書いているチームならSetup Storeに揃えると、ストアとコンポーネントで書き方を切り替えずに済みます。一方、次章の $reset() のように、Option Storeにしか無い機能もあります。Composition APIそのものの書き方はComposition APIとは?書き方とOptions APIとの違い・移行判断を解説で解説しています。

コンポーネントでの使い方:分割代入と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 と分割代入すると、その時点の値がコピーされるだけです。実測では、分割代入した count は1のまま、ストア側は2に増えていました。storeToRefs() で取り出した値はrefになり、ストアの変更に追従します。actionsはストアに束縛されているので、そのまま分割代入して呼んでも動きます。

コンポーネント外(Vue Routerのナビゲーションガードなど)でストアを使うときは、app.use(pinia) の後で呼ぶ必要があります。SSRでは、どのリクエストのPiniaかを区別するため useStore(pinia) のようにインスタンスを渡します。

stateの更新・リセット・監視:$patch・$reset・$subscribe

stateは store.count++ のように直接書き換えられます。複数の値をまとめて変えるときは $patch を使います。配列への追加のように、オブジェクトでは表しにくい変更は関数形式で渡します。

store.$patch({ count: 10 })
store.$patch((state) => {
  state.items.push('a')
})

Setup Storeに$resetが無い問題

Option Storeでは store.$reset() でstateを初期値に戻せます。Setup Storeで同じ呼び出しをすると、Pinia 4.0.3では「Store “cart” is built using the setup syntax and does not implement $reset().」というエラーになりました。公式ドキュメントは、Setup Storeでは自分で $reset を定義する方法を示しています。

import { defineStore } from 'pinia'
import { ref } from 'vue'

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回になりました。関数形式の $patch() は中で何度値を変えても1回の通知なので、通知回数を変更回数として数えることはできません。公式ドキュメントの例は、変更のたびにstate全体をlocalStorageへ保存する使い方です。永続化が目的なら、pinia-plugin-persistedstate(4.7.1、peer dependencyは pinia >=3.0.0)でストアに persist: true を付ける方法もあります。

NuxtでPiniaを使う設定

Nuxtでは公式モジュール @pinia/nuxt(最新1.0.2)を使います。公式ドキュメントは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 で指定します。SSRでは、stateのシリアライズとXSS対策をNuxtが受け持ちます。ページでデータを取得するactionは、await callOnce('user', () => store.fetchUser()) のように callOnce() で包むと、サーバーで取得済みのデータをクライアントで再取得しません。Nuxt 4のディレクトリ構成はNuxt 4とは?インストール・最新バージョン確認・Nuxt 3との違いとPinia連携まで使い方を解説で詳しく扱っています。

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のままでも書き換えられます。移行の優先度を判断する材料として、Vuexには2022年10月以降リリースが無く、新機能が追加されないことを押さえておいてください。

Piniaのテスト:createTestingPiniaの既定動作

コンポーネントのテストでは、@pinia/testing(最新2.0.1)の createTestingPinia() を使います。見落としやすいのは、既定でactionsがすべてスタブ(呼び出しを記録するだけの空関数)に置き換わる点です。Vitest 5.0.2で確かめると、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でコンポーネントテストを書く実践ガイド【2026年版】も参考になります。

Piniaを使わない方がよい場面

Piniaは導入が軽いぶん、何でもストアに入れてしまいがちです。次の場合は使わない判断をしてください。

  • 親子2〜3階層で完結するデータ:propsとemit、または provide/inject で足りる。ストアに入れると、どの画面が値を変えたかを追いにくくなる。
  • 処理の共有だけが目的:状態を持たない、あるいは呼び出し元ごとに別の状態を持つならコンポーザブルにする。同じPiniaインスタンス内ではストアIDごとに1つのインスタンスを共有する。SSRではリクエストごとにPiniaインスタンスを分ける。
  • サーバーから取ってきたデータのキャッシュ:Pinia本体のstateとactionsだけで作ると、再取得・キャッシュの破棄・読み込み中の状態を手で管理することになる。避けたいのはこのキャッシュ管理の自作で、Piniaの導入そのものではない。Piniaの作者Eduardo San Martin Morote氏が開発する @pinia/colada(1.4.6、npmの説明は「The smart data fetching layer for Vue.js」)は、Piniaを土台にこの部分を受け持つデータ取得用のライブラリで、自作より短く書ける。
  • Vue 2.6以前のプロジェクト:Vue 2.6で使う場合は、Vue 2.6対応のPinia 2.2系以前と @vue/composition-api が必要になる。Pinia 2.3以降はVue 2.7以上が必要で、3系以降はVue 2に対応しない。Vue 3への移行と合わせて検討する。

React向けの状態管理を比べる場合はZustandとは?Reactの状態管理を最小コードで実現する使い方を徹底解説【v5対応】を参照してください。PiniaはVueのリアクティブシステムの上に作られているため、Reactでは使えません。

よくある質問

Piniaの読み方は?

「ピーニャ」です。公式ドキュメントが発音を /piːnjʌ/(英語の “peenya” に近い)と示しており、スペイン語でパイナップルを意味する piña が由来です。

VuexとPiniaはどちらを使うべきですか?

新規開発ならPiniaです。Vue公式ドキュメントはVuexを保守モードとし、新しいアプリにはPiniaを推奨しています。既存のVuexプロジェクトは動き続けますが、2022年10月の4.1.0以降リリースが無いため、段階的な移行を計画してください。

Piniaの最新バージョンは?

2026年9月28日時点で4.0.3(2026年8月12日公開)です。4系は2026年7月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 を pinia.use() で登録し、ストアに persist: true を付けると、既定でlocalStorageに保存・復元されます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.23 テックブログ Renta!不正アクセスでサービス停止:再開未定の現状と運営側の停止判断・証拠保全・段階的再開
  2. 2026.10.21 テックブログ ふるさとチョイス不正アクセスで445万件流出:対象確認・パスワード変更と再開時期
  3. 2026.10.24 テックブログ カプコン不正アクセス:CAPCOM ID 1,177万件漏えいと196件の乗っ取りへの対処
  4. 2026.10.24 テックブログ ブラストメール不正アクセス:配信先288件の漏えいと利用企業の報告・通知の手順
  5. 2026.10.17 コラム カスハラ奨励金とは?東京都40万円の第2回要件と11月6日までに間に合う取組

RELATED POSTS 関連記事

目次