24
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-05

はじめに

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

Pinia ドキュメント
スクリーンショット 2021-12-04 20.27.40.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でできることは変わらないようです。

というのも開発者である [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

また、Devtoolsのサポートもされているのでデータの内容が視覚的にわかりやすくなっています↓
スクリーンショット 2021-12-05 17.43.05.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

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

所感

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

おわりに

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


参考資料

Pinia Home
Vuex の新しいライバル? Pinia のご紹介


24
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?