Vue3のリリースから、Vuexに取って代わる状態管理ツールとして誕生したPiniaについて、Vue2までメジャーだったVuexとの比較を交えながら使用方法を解説していきたいと思います。
本記事のコードサンプルは、Vue3のCompositionAPIで記述しています。「Vue2は分かるけど、Vue3はあんまり...」「CompositionAPI...?」という方はこちらの記事でVue3/CompositionAPIについて解説していますので、是非ご覧ください!✨
Piniaとは

Vue.jsのコアチームの1人でVueRouterの開発者でもあるEduardo San Martin Moroteによって開発が進められ、2019年にリリースされたコンパクトで使いやすい状態管理ツールです。
Vue2まではVuexが公式で推奨された状態管理ツールでしたが、Vue3からはPiniaを使用することが推奨されています。
発音は「ピニア」で、スペイン語でパイナップルという意味です。パイナップルが複数の花から構成される1つの果実であることが、見た目は1つの大きなストアですが実際には機能毎に細かく切り分けられるストアの構造が連想されるからだそうです(参考▶︎Why Pinia)。ロゴが話題のJSつよつよランタイムBun並みに可愛いです。
Vuexとの比較
TypeScriptとの相性Up
Vuexは、デフォルトでは型推論がサポートされていなかった為、型推論を有効にする為に一手間必要だったのですが、Piniaではデフォルトの状態で型推論までTypeScriptをフルサポートしています。
mutationsの廃止
Vuexを使用したことがある方はmutationsに馴染みがあると思いますが、PiniaではVuexのmutationsにあたる機能は存在しません。
Stateの値はMutationsでのみ行われるということを担保されるというのがMutationの良点とされていました。しかし、Pinia開発者Eduardoは「これ...いらなくない...?」となりました。その結果、Piniaでは廃止されています。
Mutationsに二度手間感を抱いていたプログラマーは結構いるのでは無いかと思います。僕もその1人でしたので、この廃止はストアをよりシンプルに、使い易くしたという印象を受けました。
インストール&初期設定手順
以下コマンドでPiniaをインストールします。
npm install pinia src/main.js(またはsrc/main.ts)を以下にように編集します。
初期化メソッドcreatePinia()をインポート/実行し、その実行結果をapp.use()に渡すことでアプリケーションにPiniaを読み込ませます。
import { createApp } from 'vue'; + import { createPinia } from 'pinia'; import App from './App.vue'; import './style.css'; const app = createApp(App); + const pinia = createPinia(); + app.use(pinia); app.mount('#app'); これでインストールは完了です!Vueのデバッグツールをブラウザにインストールしている方は、Piniaのタブが追加されていることが確認できると思います。(▶︎参考:VueDevtoolsをChromeにインストールする方法)

使用方法:ストア側
🍍ストアの定義
defineStoreメソッドを使用することで、ストアを定義することができます。このメソッドの返り値を変数に関数として格納し、それを実行することでコンポーネントからストアを利用するという流れになります。この関数はuse~Storeという名前を付けるのが一般的です。ストアを利用する為の関数という意味合です。
defineStoreメソッドには、第一引数にストアのID、第二引数で実際のストアを定義するオブジェクトを指定します。第一引数にストアオブジェクトを渡して、その中のオプションidでストアのIDを指定することも可能です。
一般的には、srcディレクトリ配下にstoreディレクトリを作成し、そこにストアのファイルを機能毎に分けて配置します。注意点として、第一引数に指定するIDは他のストアと重複しないものを指定する必要があります。重複する場合、エラーが出力されます。
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { // ストアオプション }); // または export const useCounterStore = defineStore({ id: 'counter', // ストアオプション }); 🍍state
ストアの核となる領域です。管理するデータと、その初期値を定義することができます。
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { + state: () => ({ + count: 0 + }), }); 🍍getters
stateのデータを参照する算出プロパティを定義する領域です。
Piniaではstateを直接参照することもできますが、stateのデータに一手間加えた形で取得したい場合は、gettersにメソッドを追加します。
ゲッターメソッドの中でstateにアクセスする際は、メソッドの引数にstateを取り、state.{STATE_NAME}という感じでアクセスします。
また、ゲッターメソッドの中で他のゲッターメソッドを参照する場合は、this.{GETTER_NAME}という感じで使用します。
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), + getters: { + doubleCount(state) { + return state.count * 2; + }, + doublePlusOne() { + return this.doubleCount + 1; + } + } }); 🍍actions
stateの値を更新するメソッドを定義することができます。
アクションメソッドの中でstateにアクセスする際は、this.{STATE_NAME}という感じでアクセスします。
import { defineStore } from 'pinia'; export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount(state) { return state.count * 2; }, doublePlusOne() { return this.doubleCount + 1; } }, + actions: { + countUp() { + this.count++; + } + } }); 使用方法:コンポーネント側
定義した関数をストアからインポートし、その実行結果を変数に入れることでコンポーネントにストアを取り込むことができます。その変数を通してStateの参照、Gettersの参照、Actionsの実行を行うことができます。
<template> <div> <p>Count: {{ counterStore.count }}</p> <p>Doubled Count: {{ counterStore.doubleCount }}</p> <button @click="counterStore.countUp()">Count up!</button> </div> </template> <script setup> import { useCounterStore } from '../store/count.js'; const counterStore = useCounterStore(); </script> また、VueDevtoolsをインストールしている方は、この時点でPiniaのデバッグツールにcounter(ストア定義時にIDに指定した値)が追加されていると思います。これを利用することで、ストアの状態を視覚的に把握できるようになります。

🍍storeToRefs()
たとえば以下のように実装して、Stateの値を分割代入することが可能に思えます。Actionsはこれでも機能するのですが、Stateを参照している値がリアクティブになりません。(counterStore.countUp()が実行されても画面に表示されるCount:とDoubled Count:の値が更新されなくなる)
const { count, doubleCount, countUp } = useCounterStore(); 分割代入でStateの値を変数として定義する場合は、以下のようにPiniaが提供しているstoreToRefs()というメソッドを使用しましょう。
<template> <div> <p>Count: {{ count }}</p> <p>Doubled Count: {{ doubleCount }}</p> <button @click="countUp()">Count up!</button> </div> </template> <script setup> import { useCounterStore } from '../store/count.js'; import { storeToRefs } from 'pinia'; const counterStore = useCounterStore(); // state const { count, doubleCount } = storeToRefs(counterStore); // actions const { countUp } = counterStore; </script> 🍍$reset()
Piniaではストアのstateの状態をリセットする$reset()というメソッドがサポートされています。VuexではStateの値を初期化する為にはActionsに初期化アクションを新たに追加する必要があった為、Piniaの$reset()はなかなか便利な機能です。
<template> <div> <p>Count: {{ count }}</p> <p>Doubled Count: {{ doubleCount }}</p> <button @click="countUp()">Count up!</button> + <button @click="counterStore.$reset()">Reset Store🗑</button> </div> </template> <script setup> import { useCounterStore } from '../store/count.js'; import { storeToRefs } from 'pinia'; const counterStore = useCounterStore(); // state const { count, doubleCount } = storeToRefs(counterStore); // actions const { countUp } = counterStore; </script> 