グローバルステート管理ライブラリである「Pinia」について

### はじめに
今回はグローバルステート管理ライブラリである「Pinia」について紹介と実際に自分で触ってみたいと思います。
興味を持ったきっかけとしては10月27日に開催された「v-tokyo オンライン Meetup#14」内で「Pinia」ライブラリの開発者である [Posva氏](https://github.com/posva/pinia)の発表を聞き、複雑なコードを書かずともグローバルなステートの型安全を担保してくれるという魅力もさることながら、「アイコンが可愛い！！」と思って大変興味を持ちました。


[Pinia ドキュメント](https://pinia.esm.dev/)
![スクリーンショット 2021-12-04 20.27.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507371/ec0b8661-9237-b2f0-b452-50fbe59728cf.png)


### Piniaとは
まずPiniaとは、Vue.js向けの状態管理ライブラリであり、階層が深くなったコンポーネント間でデータの共有ができるものです。Vuexと同様にコンポーネント間で状態の受け渡しが容易になるため、ある程度規模の大きい開発において威力を発揮する反面、小規模な開発or個人開発でもTypeScriptのフルサポート・記述量が少なくできるということから開発規模の大小関係なく導入するメリットは大きいと思います。


### Vuexとの違い
#### Vuex3.x 4.x
・mutationsがなくなり、stateの値を変更する場合はactionsを使用する
・TypeScriptのサポートを受け、正しく型付けされた(Vuex4.xはTypeScriptのサポートを受けています）
・機能ごとに切り分けがしやすい
・ネストされたモジュールの廃止
・名前空間付きモジュールの廃止
等があげられています。

#### Vuex.5（RFC段階）
基本的にはPiniaとVuex5でできることは変わらないようです。

:::note info
というのも開発者である [Posva氏]はVuexの設計にも携わっており、Vuex5で新しく導入する機能のテストも兼ねてPiniaを開発したという意図があるそうです。Vuex5は従来の書き方と大きく違うことが想定されており、今の段階でPiniaを使用することできたるVuex5への導入が容易になる＋Vuex5に統合する見通しがあるそうです。
:::


### 試してみる

今回はお試しということ開発サーバーの起動が早い＋高速で動作するビルドツールであるViteを使用します。また、Viteでプロジェクトを作成すると、Vue3.2から使用できる `<script setup> `構文がそのまま使用できますのでこちらの書き方で試してみたいと思います。また、今回はストップウオッチの機能を作成しますが、この機能のコードの説明は省かせていただきますのでご了承くださいませ。

まずは下記コマンドを実行し、環境のセットアップを始めます。

プロジェクト名は任意、フレームワークはvueで作成します。

```
$ npm init @vitejs/app
$ cd hoge
$ npm install
```
piniaのインストール

```
$ npm install pinia
```

main.js内にPiniaライブラリを読み込むように記述の追記。


```main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

createApp(App)
  .use(createPinia()) // この行を追加します
  .mount('#app')
```


```src/stores/timer.js
import { defineStore } from 'pinia'

export const useStore = defineStore('hoge', {
  state: () => {
    return {
     // 初期値を入れる
    }
  },
  getters: {
    //　stateのデータに対する算出プロパティを定義
  },
  actions: {
    // stateで定義したデータの更新
  },
})
```
`state` 関数を定義して戻り値に初期値を含めたオブジェクトを返してあげます。
`getters` stateのデータに対する算出プロパティを定義し、各コンポーネントで使用ができます。また、他のgetterへのアクセスもできます。
`actions` 定義するメソッド内でstateで定義したデータの更新を行います。

defineStore関数の第一引数にはアプリケーション全体でstoreを特定するためのユニークキーを記述してあげます。これにより、Chrome DevToolsのナビゲーションメニューの「Console」に使用しているstoreの名前が表示されます↓
![スクリーンショット 2021-12-05 12.13.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507371/3a2ae03e-678a-f57f-6b6b-3dcacaa4ff27.png)

また、Devtoolsのサポートもされているのでデータの内容が視覚的にわかりやすくなっています↓
![スクリーンショット 2021-12-05 17.43.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507371/a7a70fad-51e4-d063-8fed-b0e9601427d2.png)



では、中身も書いていきます。

```src/stores/timer.js
import { defineStore } from 'pinia'

export const useStore = defineStore('hoge', {
  state: () => {
    return {
      time: 0,
      timeId: 0,
      stopTime: 0,
      status: false, // 計測状態
    }
  },
  getters: {
    // 引数でstateを受け取ることができます
    toFixedTimer: (state) => state.time.toFixed(2)
  },
  actions: {
    startTimer() {
      const startTime = Date.now()
      const measureTimer = () => {
        // thisはstoreのインスタンス
        this.time = (Date.now() - startTime) * 0.001
        if (this.stopTime) {
          this.time += this.stopTime
        }
      }
      this.timeId = setInterval(measureTimer, 10)
      this.status = true
    },
    stopTimer() {
      this.stopTime = this.time
      clearInterval(this.timeId)
      this.status = false
    },
    resetTimer() {
      clearInterval(this.timeId)
      // $reset()を使用することでstateのデータを初期状態に戻すことができます
      this.$reset()
    },
  },
})

```

gettersオプション内のtoFixedTimerの引数ではstateを受け取ることができ、値を加工しています。
actions内のメソッドでstateのデータを参照する場合はthisを使ってアクセスします。また、storeのインスタンスはメソッドを持っており、その一つである$resetを使用してstateにあるデータの初期化を行なっています（その他にもdispose、onAction、patch、subscribeのメソッドがあります）

```App.vue
<script setup>
import Timer from './components/Timer.vue'
</script>

<template>
  <Timer />
</template>
```
App.vueファイルはTimer.vueのコンポーネントをimportしているだけです。

```Timer.vue
<script setup>
import { storeToRefs } from 'pinia'
import { useStore } from '../store/timer'

// useStore を呼び出すだけで、グローバルストアへのアクセスが可能
const store = useStore()
// リアクティブさを損なわない為にstoreToRefsを使用
const { toFixedTimer, status } = storeToRefs(store)
</script>

<template>
  <div>{{ toFixedTimer }}</div>
  <button
    v-if="!status"
    @click="store.startTimer">
    開始
  </button>
  <button
    v-if="status"
    @click="store.stopTimer">
    停止
  </button>
  <button @click="store.resetTimer">リセット</button>
</template>

<style scoped>
button {
  margin: 0 2px;
}
</style>
```




storeはリアクティブなものなのでそのままES6の分割代入をしてしまうとリアクティブではなくなってしまいます。そこでstoreToRefsを使用して、プロパティをrefオブジェクトに変換したオブジェクトを返してあげることでリアクティブを維持することが可能です。
各ボタンのクリックイベントでは、store.startTimerのように記述することでactionsのメソッドを呼び出しています。

![stopwatch.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507371/fa6afabb-2326-9328-be50-55abcce864ac.gif)

はい、ストップウオッチの機能が完成です。動画をGIF化するとカクカクになってしまいました。。が滑らかにするのは一旦置いておきます。笑



### 所感

アイコンが可愛くて使いやすい印象から今回触ってみましたが、mutationsがないことでよりシンプルに書けるのはいいですね。また、Vue3からcompositionAPIが導入され、機能ごとのロジックを別ファイルに切り分けやすくなりましたが、Piniaを使用することでstoreのロジックも切り分けることができるので相性は凄く良いと感じました。TypeScriptのサポートも試してみたかったのですが絶賛勉強中の為、今回は省きましたがある程度できるようになったらPiniaまたはVuex5でいつか試してみたいと思います。
Piniaが好きになったのとVuex5のリリースが楽しみになった1日でした🍍

### おわりに
記事を読んでいただき、ありがとうございました！！今回の記事で誤字、脱字、間違った内容の箇所等ありましたらご指摘いただけると大変助かります。よろしくお願いいたします。

---
### 参考資料
[Pinia Home](https://pinia.esm.dev/)
[Vuex の新しいライバル？ Pinia のご紹介](https://zenn.dev/jay_es/articles/2021-04-08-pinia)

---