VuexからPiniaへの移行：クリスマスに見直す状態管理

# 導入

こんにちは、もんすんです！

私の個人アドカレである[ひとりアドカレ2024 byもんすん Advent Calendar 2024](https://qiita.com/advent-calendar/2024/phibi-soon)の25記事目をお届けいたします🎄

ついに最終回となります。。

私が担当していたプロジェクトでは、Nuxt3をベースに開発されているものがありました。
元々は状態管理に`Vuex`を採用していましたが、最新のプロジェクト要件や技術トレンドに対応するため、`Pinia`に移行しました。
今回は`Pinia`への切り替えについて紹介していきます。

https://pinia.vuejs.org/

# VuexとPinia

## なぜVuexを利用していたか

VuexはVue.js公式の状態管理ライブラリであり、長年にわたってVueエコシステムの中核的な存在として活用されてきました。
当プロジェクトでも、当時ドキュメントが充実しており、コミュニティも活発であったことから、プロジェクト初期においては安定性と信頼性が重視され、選択されました。

## なぜPiniaに乗り換えたか

プロジェクトを進めていく中で、Vuexのいくつかの課題や、Piniaの新しい利点が浮き彫りになり、移行を検討することになりました。

PiniaはVue3とそのComposition APIを念頭に設計されています。
Vuexと比較してAPIが直感的でシンプルです。
特に、ステート、ゲッター、アクションの定義が簡潔になり、コード量が大幅に削減されました。
    
また、Vue3の登場により、Vueエコシステム全体が進化しています。
その中で、Piniaは**公式に推奨される状態管理ライブラリ**となり、今後の標準となる可能性が高いと判断しました。

## Piniaに乗り換え手順

### パッケージのインストール

以下のコマンドを実行します。
私のプロジェクトでは、データをSessionStorageに保持したかったため、`pinia-plugin-persistedstate`を入れていますが、なくても`pinia`を利用することは可能です。

```shell
npm i pinia @pinia/nuxt pinia-plugin-persistedstate
```

### ソースコードの変更

では、ソースコードの変更です。
Vuexを利用していた時は、モジュール用のソースの他に、`accessor`と`store/index.ts`という全Storeデータをまとめた処理も実装していました。

**Vuex**(変更前)

- モジュール
```ts
import { Module, VuexModule, Action, Mutation } from 'vuex-module-decorators'
export const COUNT_MODULE = 'CountModule'

import { inject } from 'vue'

@Module({
  name: COUNT_MODULE,
  namespaced: true,
  stateFactory: true,
})
export default class CountModule extends VuexModule {

  private count =  0

  get getCount() {
    return this.count
  }

  @Mutation
  public resetCount() {
    this.count =  0
  }

  @Action
  public fetchCount(){
    // 非同期でのCount取得処理など
    GetCountService.get().then((count) => {
	    this.count = count
    })
  }
}

```

- accessor.ts
```ts
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import CountModule from '~/store/count-module'

let countStore: CountModule

function initializeStores(sessionStore: Store<any>, localStore: Store<any>): void {
  countStore = getModule(CountModule, sessionStore)
}

export {
  initializeStores,
  countStore,
}
```

- store/index.ts
```ts
import { Store } from 'vuex'
import { initializeStores } from '~/store/accessor'
import CountModule, { COUNT_MODULE } from './count-module'
import VuexPersist from 'vuex-persist'

const vuexSessionStorage = new VuexPersist({
  key: 'vuex',
  storage: window.sessionStorage,
  modules: [
    UNREAD_MESSAGE_COUNT_MODULE_NAME,
  ],
})

const sessionStore = new Store({
  modules: {
    CountModule,
  },
  plugins: [
    vuexSessionStorage.plugin,
  ],
})

const initializer = (sessionStore: Store<any>) => initializeStores(sessionStore)
initializer(sessionStore)
export const plugins = [initializer]
export * from '~/store/accessor'

```

**Pinia**(変更後)

```ts
import { defineStore } from 'pinia'
import { inject, ref } from 'vue'

export const useCountStore = defineStore('CountStore', () => {

  const count = ref(0)

  const resetCount = () => {
    count.value =  0
  }

  const fetchCount = () => {
    // 非同期でのCount取得処理など
    GetCountService.get().then((newCount) => {
	    count.value = newCount
    })
  }

  return {
    count,
    resetCount,
    fetchCount,
  }
}, {
  persist: {
    storage: sessionStorage,
  }
})
```

Piniaの方がかなりシンプルに記述できていることかと思います。

# 最後に

Piniaへの移行は、コードのシンプルさだけでなく、開発体験の向上やメンテナンス性の向上にも大きく寄与しました。
Piniaの導入はスムーズでありながら、Vue3に最適化されたモダンなアプローチを提供してくれます。

ぜひ、みなさんのプロジェクトでもPiniaを試してみてください！