What is it, naokirin?

Vue.jsのストアライブラリ Piniaを使ってみる

Piniaとは

Vue.jsを使う場合に、特にSPAの開発をしていると、クライアント側でグローバルな状態を共有したいことが多々あります。 これまではVue.jsでこのような手段として、ストアライブラリのVuexが使われることが多かったかと思います。

一方でVuexは、v4以前ではFluxアーキテクチャの影響からmutationによる冗長性の高いコードになることや、TypeScriptサポートのための手順が面倒であったりしました。

Piniaは、2019年末ごろからComposition APIに合わせて開発が開始された比較的新しいストアライブラリです。特徴としてはmutationがなく、TypeScriptの型指定がされており、また型推論が極力効くようになっています*1

またVue.js Devtoolsのサポートが手厚いため、デバッグ等もしやすくなっています。

早速Piniaを使ってみる

早速ですが、Piniaを使ってみたいと思います。

プロジェクトを作成する

Vue.js v3 + Viteを利用するのであれば、以下のように npm init vue@3 <project_name> で簡単にプロジェクトをセットアップできます。

npm init vue@3 example_project

Vue.js - The Progressive JavaScript Framework

✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add Cypress for End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in /Users/naoki/work/vuejs/pinia_example...

Done. Now run:

  cd pinia_example
  npm install
  npm run lint
  npm run dev

この際に、Piniaを利用する場合、セットアップが一緒に可能です。
以下の部分でYesを選択するだけです。

✔ Add Pinia for state management? … No / Yes

Piniaを使えるようにする

さて、それでは、まずはPiniaをセットアップします。

import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
app.use(createPinia())
       .mount("#app");

これでPiniaを使うことができるようになります。

Storeを定義する

defineStore を使って、Storeを定義できます。第一引数でStoreの名称を定義し、第二引数でStore自体の定義をします。

Store自体の定義には大きく、stategettersactions の3つの項目があります。

state は、文字通り状態の定義です。
getters は、状態変更のない計算結果を返す関数の定義をします。
actions は、状態変更を伴うようなビジネスロジックの処理を定義するとされています。

import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", () => {
  //state: 状態の定義
  state: () => ({ count: 0 }),

  // getters: 状態を計算した結果を返す関数を定義
  getters: {
    doubleCount(state): number {
      return state.count * 2;
    }
  },

  // actions: ビジネスロジックや状態の変更を伴う処理を定義
  actions: {
    increment(): void {
      this.count++;
    },

    // 非同期も可能
    async registerUser(auth): Promise<void> {
      await api.post(auth);
    }
  },
});

Storeを利用する

Storeを利用するには、 defineStore の戻り値の関数を呼び出すことで利用できます。

import useCounterStore from "./counterStore.ts";

// コンポーネント内であれば、呼び出せる
const counterStore = useCounterStore();

// getterの呼び出し
const dc = counterStore.doubleCount;

// actionsの呼び出し
counterStore.increment();

比較的シンプルに利用できるようになっています。その分、自由度も高く、ある程度 actions でのみ、状態の変更をするようにといったことを守っていく必要はありそうです。

まとめ

PiniaはVue.js自体の公式プロジェクトにもなっており、また機能的にもシンプルでStoreを簡単に導入することができます。制限が強くない部分もあるので、大規模になるとルールを守るということが難しい場面もありそうですが、わかりやすいため、これまでVuexを利用していた場合でも始めやすいのではないかと思います。

*1:Vuex 5では、mutationの廃止、TypeScriptサポートなどが予定されています