👏

【Vue.js】状態管理ライブラリPiniaについて

2022/04/25に公開

Vue3が公式versionになって早二か月経ちますが、新しく公式の状態管理ライブラリになったPiniaをやっと触れたので、そのまとめです。

Piniaについて

PiniaはVue.js向けの状態管理ライブラリです。
Vue.js向けの状態管理ライブラリと言えば、Vuexが1番に思いつきますが、PiniaではVuex5向けのアイデアをいろいろ盛り込んでいて、現在のVuexで不足している部分が改修されていたりするようです。
https://pinia.vuejs.org/

Vuexとの違いについて

現状のVuex (Vuex3.x/4.x)とは公式によると下記のような違いがあります。
・mutationの廃止
・Typescriptのサポート
・ネストされたモジュールの廃止
・ネームスペースの廃止
などなどです。

試してみる

create-vueで作成したプロジェクトの中にあるCounterStoreを使っていろいろと試してみます。

記法

現在のVuexでは、state・mutation・getters・actionsと区分が分かれており、Options APIのような記法ですが、PiniaはComposition APIのような形でも書くことができます。

stores/counter.js
import { defineStore } from "pinia";
import { ref } from "vue";

// Options API風
export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCount: (state) => state.counter * 2
  },
  actions: {
    increment() {
      this.counter++
    }
  }
})

// Composition API風
export const useCounterStore = defineStore("counter", () => {
  const count = ref(0);
  
  const doubleCount = () => {
    return count.value * 2;
  };

  const increment = () => {
    count.value++;
  };

  return {
    count,
    increment,
    doubleCount,
  };
});
//Vuex
import { useStore } from "vuex"

const store = useStore();
store.state.count; //state
store.dispatch("increment"); //actions
store.getters.doubleCount(); //getters

//Pinia
import { useCounterStore } from "./stores/counter"

const store = useCounterStore();
store.count; //state
store.increment(); //actions
store.doubleCount(); //getters

ネームスペースが廃止されたことで呼び出しの際もシンプルになりました。
Vuex側は呼び出しが長くなる傾向がありましたが、Piniaのように分割されていれば、その心配もなく、わかりやすいと思います。

他のstoreを使用する場合

Vuexではモジュールから親を呼び出す際にRootStateやRootGettersを呼び出して使用していましたが、Piniaではstore自体が分かれているため、実装も少し異なります。
例えば、ユーザー情報を使用するような例だとこのような書き方ができます。

export const useUserStore = defineStore('user', () => {
  const users = ref([{
    id: 1,
    firstname: "Taro",
    lastname: "Yamada"
  }])
  
  return { user }
});

export const useStore = defineStore('main', () => {
  const getUsers = () => {
    const userStore = useUserStore()
    return userStore.users
  };
  
  return { getUsers }
});

例としてはあまり良いものでなくて申し訳ないのですが、他の例だと、APIリクエスト時にtokenを別のstoreから持ってくるとかだとわかりやすいと思います。

共通のメソッド

Piniaでは、mutationが廃止された代わりにsubscribeやonActionなどの便利なメソッドがあります。その中のいくつかを紹介します。

$patch

Vuexのmutationのようなものです。
stateの変数を1つずつではなく、いくつかまとめて変更する際に使用します。
オブジェクトで変更する形式と関数を使って変更する2パターンがあります。

const store = useCounterStore();

//オブジェクト
store.$patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

//関数
cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

$subscribe

stateの変更を監視するメソッドです。
公式ではここで、localStorageにstateを保存する例が書かれていました。

const store = useCounterStore();
store.$subscribe((mutation, state) => {

  // mutation.events : 変更された値の情報等を持つ
  // mutation.storeId : store作成の際に設定したユニークキー
  // mutation.type :  'direct' | 'patch object' | 'patch function'
  
  console.log(mutation, state);
});

$onAction

actionsの実行を追跡します。
loadingの処理などにも使えそうですし、使い道が多そうです。

store.$onAction(
  ({
    name, // アクションの関数名
    store, // storeインスタンス
    args, // 引数
    after, // return or resolve後のフック
    onError, // エラー時のフック
  }) => {
    // Action実行前
    const startTime = Date.now()
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 実行完了後の処理
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // エラー時の処理
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

$subscribe, $onActionはデフォルトではunmounted時に追跡が解除されますが、それぞれの第二引数が設定値(デフォルトはfalse)となっていますので、trueにすればunmounted以降も追跡されます。

このほかにもいろいろなメソッドがありますので、気になる方は公式ドキュメントを見てみてください。

感想

使ってみた感想として、mutationがないというのが少し違和感を感じたり、VuexでいうところのRootStateや他のモジュールを使用したいとなったときに結局、そのstore内で別のstoreを呼び出さないといけないので、その部分には慣れない部分があります。

Vueでフロントエンドに入ったので、Vuex以外の状態管理をあまり触ってこなかったのが理由かもしれません。

ただ、Vuexと比較してもsubscribeやonActionなど便利な機能は多いので今後は当分Piniaを使ってみようかなと思います。

Discussion