Piniaとは、Vue.jsの状態管理ライブラリです。
Piniaは、「ピーニャ」と読みます。
Vue.js 2 まではVuexがVue.jsの状態管理ツールとして利用されてきましたが、Vue.js 3の公式ドキュメントではこのPiniaが状態管理ツールとして推奨されています。
今回はこのPiniaについて使い方やVuexと比較など、フロントエンド専門のプログラミング学習ツール『Skilled(スキルド)』を運営するスキルド編集部が徹底解説していきます。
目次
そもそも状態管理とは何なのか?
Piniaの解説をする前に状態管理とは何かをおさらいしておきましょう。
Vue.jsアプリケーションでは、コンポーネント間でデータを共有する必要がよくあります。propsを通じてデータを渡し、イベントを発行することもできますが、アプリケーションが成長するにつれてこれが煩雑になります。
状態管理はこの問題を解決する為に、アプリケーションのコンポーネント間で共有されるデータを管理するための場所を提供するデザインパターンの実装です。
ユーザーに提示されるデータ全体の一貫性を確保するために利用され、デバッグとメンテナンスを容易にします。
SPAでは、ページという概念ではなく複数のコンポーネントでデータを操作し変更してからサーバーにAPIを通じてデータを送ります。これらの変更を管理する方法として『データの状態』が必要であり、VuexやPiniaなどの状態管理ライブラリを利用しています。
VuexやPiniaではデータはストアと呼ばれる場所で一元管理されるので、個々のコンポーネントでデータを持つ必要がなくなるというメリットもあります。
Vue.jsにおける状態管理ツールといえば、Vuexが有名であり公式からも推奨されていましたが、2020年9月にリリースされたVue3からはPiniaが状態管理ツールとして推奨されています。
以下は2021年にトロントで開催されたVue Conf 2021年のTweetですが、Vue.jsの開発者であるEvan You氏の講演時にVue3の推奨状態管理ツールがPiniaになったことを公言しています。
🔥 Beware!
The new @vuejs default recommendations!
Vue CLI ➡️ create-vue (npm init vue@next)
Vetur ➡️ Volar
Vuex ➡️ Pinia pic.twitter.com/dYcQpMvZ0R— VueDose (@VueDose) November 23, 2021
Piniaの使い方
Piniaの導入方法と設定
PiniaでState管理するまでの導入方法を見ていきます。
今回はVue.js 3 + Vite + Pinia + TypeScript の環境を作成し、PiniaでState管理できるまでの導入方法を見ていきます。
基本的にはState管理までの導入方法はVuexとほとんど違いはなく、以下の4ステップState管理まで実装可能です。
- Piniaのインストール
- VueにPiniaをセットアップ
- storeファイルの作成
① Piniaのインストール
まずはPiniaをインストールします。
npm であれば、
npm install pinia
yarn を利用する場合は以下のでインストールできます。
yarn add pinia
② VueにPiniaをセットアップ
次にmain.tsにPiniaをセットアップします。
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
createApp(App).use(createPinia()).mount("#app");
上記のようにcreatePiniaをimportし、VueのcreateApp時にuseメソッドでcreatePiniaを実行してマウントするだけでPiniaのセットアップ完了です。
③ storeファイルの作成
次にstoreファイルを作成します。
import { defineStore } from "pinia";
export const useStore = defineStore({
id: 'store',
state: () => ({
person: {
name: '',
company: ''
}
})
});
ポイントとしてはidの値はユニークである必要があります。
idが重複していると、どちらかのstoreしか読み込まないようになっているのでエラーが起こります。
④ コンポーネントで表示
最後のstoreのstateで管理しているstateをコンポーネントで表示します。
表示方法も非常にシンプルでstoreファイルをインポートし、定義したstoreを呼ぶだけでstateを表示させることが可能です。
<script setup>
import { useStore } from "./store/index";
import { useCounterStore } from "./store/counter";
const store = useStore();
</script>
<template>
<div>会社名: {{ store.person.company }}</div>
<div>氏名: {{ store.person.name }}</div>
</template>
VuexからPiniaへの移行
現在はVuexを使用しており、Piniaへの移行方法を知りたい方はこちらの記事で詳しく解説しております。
Piniaでの基本的な状態管理方法
まずは、Piniaでのストアの定義を見ていきましょう。
Piniaでは、
- State
- Getters
- Action
の3つの要素からストア(状態管理する場所)が出来ています。
defineStore 関数
Piniaでは、defineStore
関数を使用してストアを定義します。この関数は、ストアの状態(state)、ゲッター(getters)、アクション(actions)などを管理します。
Pinia のストア定義には、必ず一意のid
を定義する必要があります。id
は、defineStore
関数の第一引数に定義します。以下の例では、myStore
という文字列がストアのid
となります。
// コードブロック
import { defineStore } from 'pinia';
export const useStore = defineStore('myStore', {
state: () => ({
count: 0,
}),
getters: {
doubleCount(state) {
return state.count * 2;
},
},
actions: {
increment() {
this.count++;
},
},
});
次に、ストアの状態(state)、ゲッター(getters)、アクション(actions)それぞれの役割についてご紹介していきます。
State
Stateは管理するデータの入れ物の役割を持っています。
ここに定義したデータは Vueアプリケーション内の各コンポーネントから適宜取得・更新することが可能です。
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
state: () => {
return {
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
Getters
getters では state のデータに対する算出プロパティを定義します。
PiniaではStateから直接データを取得することも可能ですが、Stateにあるデータをソートして取得したいなどStateから取得するデータに一手間加えて取得したい場合にはGettersを利用します。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
})
Action
Actionプロパティでは、stateの値を更新する際に利用します。
APIでデータを取得してstateに格納する、stateに入っているデータを更新するといったビジネスロジックを定義します。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
actions: {
increment() {
this.counter++
},
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
},
})
Piniaの特徴とVuexとの比較
同じVue.jsの状態管理ライブラリであるVuexとは明確に4つの違いがあります。
- TypeScript のフルサポート
- mutations の廃止
- ネストされたモジュールの廃止
それぞれ詳しく見ていきます。
TypeScript のフルサポート
Vue3にも対応しているVuex4.xではTypeScriptにTypeScriptに対応はしているものの完全にサポートされている訳ではありませんでした。
TypeScriptの型推論がサポートされておらず、対応させるためにはカスタムでラッパークラスを実装する必要がありました。
Piniaでは型推論までTypeScriptをフルサポートしているので、デフォルトでTypeScriptをを活かせる設計になっています。
mutations の廃止
Vuexでstateの値を更新する際にミューテーションで行っていましたが、Piniaではこのミューテーションは廃止されました。
VuexのStateの更新はミューテションでしか行えないという仕様でしたが、PiniaではどこからでもStateの更新を行うことが可能です。
このmutationを廃止すべきかはPinia のコミュニティでも議論されていました。
Add an option to disallow direct state modification from component
mutationがあることで、データの書き換えを限定し厳格に管理することを担保していました。
しかし、結局はactionメソッドを通してただstateの値を変更するという単一の操作しか行われていないためmutationは完全に不要だったとPiniaの開発者でありVue.jsのコアチームの一人でもある『Eduardo San Martin Morote』が言っています。
廃止によって可読性が高いコードに
VuexではState、Getters、Mutation、Actionとストアを扱う上では4つの構造を記述しなければならないためにコードが冗長していくの一つの問題でもありました。
Piniaではmutationの廃止され、全体的な記述の見直しが行われておりVuexと比較すれば可読性の高いコードになっています。
以下はVuexを使ったストアの記述です。
import { createStore } from "vuex";
const useCounterStore = createStore({
state() {
return {
count: 0,
};
},
mutations: {
setCount(state, count) {
state.count = count;
},
},
actions: {
increments({ commit }, currentNum) {
commit('setCount', currentNum++)
}
},
続いてこちらが、Piniaのストアです。
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
}),
actions: {
increments({ state }, currentNum) {
state.count = currentNum;
},
},
});
このようにmutationが廃止された分、スッキリし可読性が高いコードになっています。
ネストされたモジュールの廃止
Vuexでは複数のモジュールを1つのストアに持ち単一でストアを持つ概念でした。
1つのストアにまとめることで、名前空間を作成したり相互にネストしストアを構成していました。
しかし、ネストされたモジュールの構成はpiniaでは廃止され、以下のようにフラットにストアを持つ構成になっています。
Piniaでは一つ一つのstoreが分離されており、呼び出す際も該当のストアモジュールを直接呼び出しますが、Vuexでは単一のモジュールに集約したストアで管理する構造になっています。
ストアを格納するVuexでは『store』ディレクトリですが、Piniaでは『stores』と名付けられています。
これはPiniaがストアを単一のモジュールとしてではなく複数のモジュールとして扱っていることを強調するために命名しているそうです。
VuexからPiniaへの移行
現在 Vuex を使っている方でも、Vue.js 3 の開発環境であれば Pinia へ移行することが可能です。
VuexからPiniaへのマイグレーションプロセスを解説します。
ステップ1:依存関係の更新
最初に、プロジェクトからVuexをアンインストールし、Piniaをインストールします。
npm uninstall vuex
npm install pinia
ステップ2: ストアの書き換え
次に、既存のVuexストアをPinia形式に書き換えます。以下は、VuexとPiniaでのストアの定義の違いです。
Vuex
Vuexでは、state
, mutations
, actions
, getters
などを一つのオブジェクトにまとめて定義します。
// Vuexの例
import Vuex from 'vuex';
export default new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++;
},
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
},
getters: {
doubleCount: state => state.count * 2,
},
});
Pinia
Piniaでは、defineStore
関数を使用してストアを定義します。state
, actions
などはメソッドとして定義します。
// Piniaの例
import { defineStore } from 'pinia';
export const useStore = defineStore({
id: 'main',
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
async incrementAsync() {
await new Promise(resolve => setTimeout(resolve, 1000));
this.increment();
},
},
getters: {
doubleCount() {
return this.count * 2;
},
},
});
ステップ3: APIの変更
VuexのAPI(commit
, dispatch
など)をPiniaのAPIに変更します。
Vuex
this.$store.commit('increment');
Pinia
const store = useStore();
store.increment();
VuexからPiniaへの移行方法は公式ドキュメントで解説されていますので詳しく知りたい方はそちらをご覧ください。
まとめ
Piniaは最新バージョンのVue3だけでなくVue2でも利用することが可能です。
Vuexと比較すると一番大きな違いは mutationが廃止されシンプルに書けるようになったという点でしょうか。
また、TypeScriptもフルサポートされており型推論が使えるというのも個人的には嬉しい点です。
Vue3を利用する際は状態管理ライブラリとしてぜひPiniaをお試しください。
Piniaについてご紹介しました。
Vue.js を学ぶなら Skilled | スキルド
Skilled(スキルド)は、実践特化型のプログラミング学習サービスです。Vue.js のオフィシャルパートナーも努めており、Vue.jsが学習出来るサービスを提供しています。
あなたの学習効率を最大限スキルに転換する為に、特許技術も含め学習に関する様々な発明をしています。
マークアップからアプリケーションエンジニアまでの学習をカバーしており、コーディングしながら実践的に学習することで知識ではなく『スキル』が身につけられるサービスとなっております。
現在無料体験キャンペーンを行っていますので、気になる方はぜひ無料体験を利用してみてください。