Laravel9/Vue3環境で状態管理ライブラリPiniaを試す

Vueでの状態管理ライブラリと言えばVuexだったけど、今やPiniaが公式ライブラリらしい。mutationsなどややこしいものが排除されてシンプル!簡単に実装できそうなのでLaravel9/Vue3環境で使い方を確認してみよう。

開発環境

Windows10
Docker Desktop
WSL2
Laravel9
Laravel Sail
vue v3.2.40
pinia v2.0.23

Piniaのインストール

$ sail npm install pinia

Vue3インスタンスへのPinia組み込み

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './components/App.vue'

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

ストアの作成 defineStore

defineStore(ストア名, プロパティ)

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('counter', {
  // プロパティ設定[state, getters, actions]
  …
})

State:状態管理対象

状態管理対象の設定と状態取得をしてみましょう。

Stateの設定

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('counter', {
  state: () => ({
    count: 1,
  })
})

stateの取得

<template>
  <h1>Counter</h1>
  <p>Count: {{ counter.count }}</p>
</template>

<script setup>
  import { useStoreCounter } from '@/stores/counter'
  const counter = useStoreCounter()
</script>

Getters:状態を利用したメソッド

状態(state)を利用して取得するメソッドの作成と利用方法を確かめる。
引数にstateをとることができるのね。

Gettersの設定

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('counter', {
  state: () => ({
    count: 100,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})

Gettersの利用

<template>
  <h1>Counter</h1>
  <p>Count: {{ counter.count }}</p>
  <p>doubleCount: {{ counter.doubleCount }}</p>
</template>

<script setup>
  import { useStoreCounter } from '@/stores/counter'
  const counter = useStoreCounter()
</script>

Actions:状態変更メソッド

stateを変更するメソッドをactionsに作成
「this」でstateにアクセスするのでアロー関数は使用できないのね。

Actionsの作成

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('counter', {
  state: () => ({
    count: 100,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    },
  },
})

Actionsの利用

<template>
  <h1>Counter</h1>
  <p>Count: {{ counter.count }}</p>
  <p>doubleCount: {{ counter.doubleCount }}</p>
  <div>
    <button @click="counter.increment">++</button>
    <button @click="counter.decrement">--</button>
  </div>
</template>

<script setup>
  import { useStoreCounter } from '@/stores/counter'
  const counter = useStoreCounter()
</script>

メソッド

$resetメソッド

stateを初期値にリセット

<button @click="counter.$reset()">Reset</button>

$patchメソッド

stateを任意の値に更新

const patch = () => {
  counter.$patch({
    count: 100,
  })
}

動作テスト

分割代入

Actionsはそのまま分割代入で取得可能。しかし、stateやGettersは取得できないのでstoreToRefs関数を通すこと。

<template>
  <h1>Counter</h1>
  <p>Count: {{ count }}</p>
  <p>doubleCount: {{ doubleCount }}</p>
  <div>
    <button @click="increment">++</button>
    <button @click="decrement">--</button>
  </div>
</template>

import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
const counter = useStoreCounter()
const { increment, decrement } = counter
const { count, doubleCount } = storeToRefs(counter)

CompositionAPIでの書き方

これまでOptionsAPIで記述してきたが、CompositionAPIでも可能。

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

export const useStoreCounter = defineStore('counter', () => {
  // State
  const count = ref(1)

  // Getters
  const doubleCount = computed(() => count.value * 2)

  // Actions
  const increment = () => {
    count.value++
  }
  const decrement = () => {
    count.value--
  }

  return {
    count,
    doubleCount,
    increment,
    decrement,
  }
})

永続化

別Componentに移動しても、別Componentで取得しても、もちろん同じ値が取得できるが、リロードしたらリセットされてしまう。以下に紹介する永続化プラグインのいずれかを使用することでリロードされても値を保持することが可能。

pinia-plugin-persistedstate

https://github.com/prazdevs/pinia-plugin-persistedstate

インストール

$ sail npm install pinia-plugin-persistedstate

app.js

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

ストアでの設定

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('conter', {
  state: () => {
    …
  },
  persist: true,	// 追加
})

pinia-plugin-persist

インストール

$ sail npm install pinia-plugin-persist

app.js

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'

const pinia = createPinia()
pinia.use(piniaPersist)

createApp({})
  .use(pinia)
  .mount('#app')

ストアでの設定

import { defineStore } from 'pinia'

export const useStoreCounter = defineStore('conter', {
  state: () => {
    …
  },
  // 追加
  persist: {
    enabled: true
  }
})

vue-routerでPiniaを使用する場合の注意

Piniaをvue-routerのナビゲーションガードで利用する場合、以下のようにbeforeガード内でストアを呼び出す。

import { useUserStore } from '@/stores/user'
…
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  ...
}

beforeガードの外で呼び出した場合、以下のようなエラーが出力された。

Uncaught Error: [*]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
	const pinia = createPinia()
	app.use(pinia)
This will fail in production.

https://pinia.vuejs.org/core-concepts/outside-component-usage.html#single-page-applications