Nuxt3でPiniaを使って状態管理を実装してみた

## はじめに
Nuxt3でPiniaを使って状態管理を実装してみました。

PiniaはVueで扱うグローバルなデータの状態を管理するための状態管理ツールです。Vue2まではVuexがデフォルトのVueの状態管理ツールでしたが、Vue3ではPiniaの利用が推奨されています。
この記事では、Piniaを使用してファイル間での状態共有を実現し、さらに pinia-plugin-persistedstate/nuxt プラグインを利用して、ページをリロードしても状態が保持される機能を実装します。

## 開発環境
- Windows 11
- Nuxt.js 3.4.1
- Vuetify 3.4.0-alpha.0
- npm 8.19.4
- Node.js 16.20.0

## ディレクトリ構造
本記事で使用するプロジェクトのディレクトリ構造です。ファイルの作成については、実装を進めながら必要に応じて行いますので、現時点では作成されなくても構いません。
```
[プロジェクト名]/
├── composables/
│   └── states.ts     
├── pages/
│   ├── index.vue      
│   └── count.vue     
├── plugins/
│   └── vuetify.ts  
├── nuxt.config.js 
└── package.json     
```

## 実装
Nuxt3プロジェクトを作成します。
```
npx create-nuxt-app [プロジェクト名]
```
　　
作成したプロジェクトのディレクトリに移動します。
```
cd nuxt-test
```
　　
Piniaをインストールします。

```
npm i pinia @pinia/nuxt
```
　　
[公式](https://pinia.vuejs.org/ssr/nuxt.html)によると、npmでインストールした場合、依存関係のエラーが発生することがあるようです。私は発生しました。その場合は、package.jsonにVueの"overrides"を追加してください。

```json:package.json
{
  //中略

  "devDependencies": {
    "@pinia-plugin-persistedstate/nuxt": "^1.1.1",
    "mdi": "^2.2.43",
    "nuxt": "^3.4.1",
    "sass": "^1.66.1",
    "vuetify": "^3.4.0-alpha.1"
  },
  "dependencies": {
    "@pinia/nuxt": "^0.4.11",
    "pinia": "^2.1.6"
  },
	//以下を追加
  "overrides": {
    "vue": "latest"　
  }
}
```
　　
次に、Piniaが使用できるように、nuxt.config.tsのmodulesにPiniaを追加します。

```jsx:nuxt.config.ts
import { defineNuxtConfig } from "nuxt/config"

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
　
	//中略

　//追加
  modules: ['@pinia/nuxt'],
});
```
　　
composables\states.tsを作成し、以下のようにします。composablesディレクトリでは、コンポーネントのスクリプトの一部を別のファイルに切り出して、部品として再利用することができます。
```jsx:composables\states.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore(
    "counter",
    () => {
        const count = ref(0);
        function increment() {
            count.value++;
        }
        return { count, increment };
    },
);
```
defineStore関数はPiniaライブラリから提供され、ストアを定義するために使用されます。ストアはアプリケーションの状態管理を行うために使用され、ファイル間でデータを共有できます。
ストアの定義部分では、countと、countを増やすためのincrement関数を定義しています。
　　
次に、pages\index.vueを作成し、以下のようにします。

```jsx:pages\index.vue
<script setup>
const counter = useCounterStore();
</script>

<template>
  <v-container>
    <nuxt-link to="/count">count ページへ飛ぶ </nuxt-link> //countページに遷移する
    <p>ボタンを{{ counter.count }}回おしました!</p>
  </v-container>
</template>
```
scriptタグ内でcomposables\states.ts内のuseCounterStoreを呼び出し、その中のcount変数を表示させています。
countページはこの後作成します。
  
次に、countページにあたるpages\count.vueを作成し、以下のようにします。
```jsx:pages\count.vue
<script setup>
import { storeToRefs } from "pinia";
const counterStore = useCounterStore();
const { increment } = counterStore;
const { count } = storeToRefs(counterStore);
</script>

<template>
    <v-container>
        <h1>Count:{{ count }}</h1>
        <v-btn @click="increment">+1する</v-btn>
        <nuxt-link to="/" class="ml-4">もどる</nuxt-link>
    </v-container>
</template>
```
「+1する」ボタンを押すと、composables\states.ts内のuseCounterStoreで定義したincrement関数が呼び出され、ボタンを押すごとにcount変数が１ずつインクリメントされる仕組みです。
　　
コマンドで npm run dev を実行すると以下のような画面が表示されます。

![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/8901fad1-1884-3b8b-c83d-eeddb96ef094.png)　
まだボタンを押していないので、初期値の0が表示されています。
  
ここで、「count ページへ飛ぶ」リンクをクリックすると、countページに遷移することができます。そして、count ページに表示されている「+1する」ボタンをクリックすると、countが1ずつインクリメントされます。
以下は「+1する」ボタンを2回押したときの画面です。
![Untitled (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/d5f84cb8-fe6a-5c8b-e4bf-4668528e89f5.png)

「もどる」をクリックすると、こちらにもcount変数が反映されていることが確認できます。
![Untitled (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3124507/52d68e68-a750-0279-d983-01408c39dc6d.png)


ただし、現時点ではページをリロードするとcount変数が初期値の0に戻ってしまいます。
そこで、pinia-plugin-persistedstateをインストールして、リロードしても状態が保持されるようにしていきます。

サーバーを停止し、以下のコマンドを実行してください。

```
npm i -D @pinia-plugin-persistedstate/nuxt
```
　　
インストールしたプラグインが使用できるように、nuxt.config.tsのmodulesにpinia-plugin-persistedstateを追加します。
```jsx:nuxt.config.ts
import { defineNuxtConfig } from "nuxt/config"

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
　
	//中略

  modules: [
        '@pinia/nuxt',
        '@pinia-plugin-persistedstate/nuxt' //追加
    ],
});
```
　　
composables\states.tsにpersistを追加して、リロードしても状態が保持されるようにします。
　　
※セッションストレージに保存する場合
```jsx:composables\states.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore(
    "counter",
    () => {
        const count = ref(0);
        function increment() {
            count.value++;
        }
        return { count, increment };
    },

		//persistを追加して状態を保持する
    {
        persist: {
            //セッションストレージに保存する場合
            storage: persistedState.sessionStorage,
        },
    }
);
```
　　
※ローカルストレージに保存する場合
```jsx:composables\states.ts
import { ref, computed } from "vue";
import { defineStore } from "pinia";

export const useCounterStore = defineStore(
    "counter",
    () => {
        const count = ref(0);
        function increment() {
            count.value++;
        }
        return { count, increment };
    },

		//persistを追加して状態を保持する
    {
        persist: {
            //ローカルストレージに保存する場合
            storage: persistedState.localStorage,
        },
    }
);
```


セッションストレージに保存する場合、ブラウザやタブを閉じると値が破棄されます。
ローカルストレージに保存する場合、キャッシュクリアを明示的に行わない限り、ブラウザやタブを閉じても状態は保持されます。
どちらに保存するかは、目的に合わせて選んでください。
状態を永久に保持したい場合は、別途データベースなどを構築する必要があります。

　　
npm run dev でサーバーを立ち上げ、先ほどと同じようにインクリメントを実行した後、リロードしてみてください。リロードしても状態が保持されていれば成功です。


## 終わりに
今回は、Nuxt3でPiniaを使って状態管理を実装してみました。
Vuexより記述がシンプルで個人的には好きです。
最後までお読みいただき、ありがとうございました！


## 参考記事
https://zenn.dev/mihorin1729/articles/f29073e516bc8c

https://pinia.vuejs.org/ssr/nuxt.html