Help us understand the problem. What is going on with this article?

【Vue3】Composition APIを使ったVuexの代替

はじめに

Vue3から使えるようになるComposition APIでは、リアクティブなデータの管理はVueコンポーネントから解放されて、それぞれのロジックをパーツとして分解しComposition(合成)できるようになりました。

これは、グローバルなリアクティブな状態の管理ができるようになったことを意味します。ともすれば、今まで上位管理の手法としてメジャーな存在であったVuexにCompsition APIはとって変わる存在になりえるのではないのでしょうか?
Compositon APIを利用した、小さなストアの管理を見ていきます。

ストアの作成

はじめにストアを作成します。
reactiveメソッドを利用して、リアクティブな状態を作成しました。ここでは、Vuexの作法に従ってstateは直接更新できないようにreadonlyにしています。

stateの値を更新するためには、increment()decrement()メソッドを利用します。

src/store/index.ts
import { InjectionKey, reactive, readonly } from 'vue'
import { Store } from '@/types/store'

const state = reactive({
  count: 0
})

const increment = () => state.count++

const decrement = () => state.count--

export default {
  state: readonly(state),
  increment,
  decrement
}

export const key: InjectionKey<Store> = Symbol('key')

さらに、provide/injectをするために必要なキーを用意します。キーには文字列かSymbolで定義できますが基本はSymbolです。(Symbolは、一意な値を返すデータ型です。)

InjectionKeyをジェネリクスで型指定をすると、provide/injectionをした際に型検査が効くようになります。Storeの型定義も定義しておきます。

src/types/store.d.ts
export interface Store {
  state: {
    readonly count: number;
  };
  increment: () => number;
  decrement: () => number;
}

ルートコンポーネントでストアをprovideする

ストアをグローバルで利用したいので、ルートコンポーネントからprovideします。
provide(key, value)を受け取り、provideされた値は子コンポーネントからキーを用いてinjectすることで取り出すことができます。

src/main.ts
import { createApp, provide } from 'vue'
import App from './App.vue'
import store, { key } from '@/store'

createApp({
  ...App,
  setup () {
    provide(key, store)
  }
}).mount('#app')

コンポーネントからinjectでストアにアクセス

それでは、provideされたストアにinjectでアクセスしてみましょう。
値はinject(key)とキーを指定して取り出します。取り出した値の型はStore(InjectionKeyのジェネリクスで指定した型) | undefinedですから、型ガードを用いて確認する必要があります。(誤ったキーを渡した場合や、自身より上の階層でprovideされていなかった場合にundefinedが返されます。)

Counter.vue
<template>
  <div>{{ store.state.count }}</div>
  <button @click="store.increment">Increment</button>
  <button @click="store.decrement">Dncrement</button>
</template>

<script lang="ts">
import { computed, defineComponent, inject } from 'vue'
import { key } from '@/store'

export default defineComponent({
  setup () {
    const store = inject(key)

    if (!store) {
      throw new Error('')
    }

    return {
      store
    }
  }
})
</script>

終わりに

今回は、Vuexの代替を目的としてストアを作成したので、グローバルな状態で定義しましたが、provideはルートコンポーネント以外の場所からもできるので、範囲を限定してストアを定義することも可能です。Vuexはグローバルに定義せざるをえなかったのですから、良い点と言えるのではないでしょうか。

さらに、Vuexの持っていた型課題を解決しただけでなく、抽象的な型に依存することにも成功しています。Vue2.2から存在していたもののプラグインやライブラリ以外への使用は推奨されていなかったprovide/injectionですが、データやメソッドがコンポーネントに縛られなくなったので、真価を発揮することが可能になりました。

ただし、Vuexを利用し続けることにも依然メリットは存在します。
Vue Devtoolsを利用したVuexのデバッグはすべてのミューテーションが追跡されており、の履歴が時系列順に表示されたり、Devtool上で値を更新できたりと魅力的です。

また、vuex-persistedstateなどの豊富なライブラリも存在します。これらを手放すには、時期尚早ともいえるでしょう。

参考

Should You Use Composition API as a Replacement for Vuex?
Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか?
Vue 3.0時代の状態管理
Vue Composition API + TypeScriptで DI(依存性の注入), DIP(依存性逆転の原則) を実装してみる

azukiazusa
フィットボクシングも始めました。
https://sapper-blog-app.vercel.app/blog
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away