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

# はじめに

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

これは、グローバルなリアクティブな状態の管理ができるようになったことを意味します。ともすれば、今まで上位管理の手法としてメジャーな存在であった[Vuex](https://vuex.vuejs.org/ja/)に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](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/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`が返されます。）



```html: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](https://github.com/robinvdvleuten/vuex-persistedstate)などの豊富なライブラリも存在します。これらを手放すには、時期尚早ともいえるでしょう。

# 参考

[Should You Use Composition API as a Replacement for Vuex?](https://medium.com/js-dojo/should-you-use-composition-api-as-a-replacement-for-vuex-274356443ebc)
[Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか？](https://qiita.com/tmy/items/a545e44100247c364a71)
[Vue 3.0時代の状態管理](https://shiodaifuku.io/articles/3MybyC5EmKmSIY8fPQBR)
[Vue Composition API + TypeScriptで DI(依存性の注入）, DIP（依存性逆転の原則） を実装してみる](https://qiita.com/ryo2132/items/03380df2df5b4b2933d7#-vue-composition-api--typescript%E3%81%A7%E3%81%AE%E5%AE%9F%E8%B7%B5)