Vuex の Payload を推論型で守る

## ⛑ 型で何を守るのか？

昨今、フロントエンドで静的型付けの需要が高まっていますね。各位導入事例も増えてきていることと思いますが、Vuex で TypeScript を導入する前に、なぜ型が必要なのかを再確認したいと思います。

Flux はモジュールの分散がつきものです。リリース当初は画面も確認しているし、小さいコードなのでミスには直ぐに気づきます。しかし、アプリケーションが肥大化し、様々なメンバーがコードを触る頃に問題が明るみに出ます。Flux は各所 State / Payload に強依存しており、肥大化したコードではその依存を把握しきれず、リファクタ不能に陥ります。

|リリース前|リリース後|
|:-:|:-:|
|依存の分散|考慮漏れエラー|
|![inferred_types_for_flux1.png](https://qiita-image-store.s3.amazonaws.com/0/59071/79036421-a15f-7357-fc46-f85956711d66.png)|![inferred_types_for_flux2.png](https://qiita-image-store.s3.amazonaws.com/0/59071/dcaeb43e-9c0a-1f29-b090-73a49d181c0b.png)|

運良くテストで検知出来れば良いのですが、全ての現場でテストが十分に揃っているとは限りません。また、ここの担保のためだけにテストを書くことも、開発工数に見合わなくなってきます。ここで、一つの問題を解決するモジュールを紹介します。

[vuex-aggregate](https://www.npmjs.com/package/vuex-aggregate) は commit / disptch の空振りと、それらの Payload を **推論型** で守ることを提供しています。[README](https://github.com/takefumi-yoshii/vuex-aggregate) に記したとおり、まずは VSCode 等でリファクタのシミュレートをしてみてください。開発サーバーを立ち上げるまでもなく、直さなければいけない箇所にすぐに気づくことが出来ます。

プロダクションコードの健全性を保つために、リファクタは常に必要です。**「型はリファクタによる事故を防ぐためにある」**と言っても過言ではないと筆者は考えています。

## 🔧 mutation / action 名称をリファクタ

vuex-aggregate で提供している `fromMutations` / `fromActions` で生成された commits / dispatches には推論型がかかっており、利用出来る commit / dispatch が予約されます。VSCodeなどでは`commits.`まで入力するとコードヒントが出てきますね。これは型による副産物ですが、DXがかなり向上します。

開発者が定義した mutations / actions に proxy を通し、処理行程で推論型を導出する仕組みです。mutations / actions の関数名を変更すると、それを引き出して利用している commits / dispatches で「そんなものは無いよ」と怒ってくれます。これで臆することなく関数名を変更することが出来ます。

```counter.ts
increment(state: State): void {
  state.count++
}
```

上記 mutation を以下の様に変更してみます。いくつエラーが見つかったでしょうか？ SFC でも反応していることが分かり、型無しの Vuex がいかに危険か察せるかと思います。

```counter.ts
addCount(state: State): void {
  state.count++
}
```

## 🛠️ Payload型 をリファクタ

mutations / actions の第2引数に定義した payload型をリファクタすることもあるかと思います。vuex-aggregate は TypeScript2.8 からの新機能（Type inference in conditional types）を利用しており、定義元の mutations / actions 第2引数型から commits / dispatches の payload型へと推論導出しています。これは、つい最近出来る様になった機能で、TypeScript ならではの芸です。
[> 導出定義はこちら](https://github.com/takefumi-yoshii/vuex-aggregate/blob/master/typings/index.d.ts)

```counter.ts
setName(state: State, name: string): void {
  state.name = name
}
```

上記 mutation の Payload型を以下の様に変更してみます。ここから生成された commit関数の payload型が追従し、エラーを確認することが出来ます。

```counter.ts
setName(state: State, payload: { name: string }): void {
  state.name = payload.name
}
```
他にも「number」にしてみるなど、挙動を確認してみてください。型の必要性を再確認できるかと思います。

## 🗑️ MutationTypes は書かない

example の通り、`fromMutations` に mutations と namespace を与えることで、MutationTypes も自動生成されます。

```counter.ts
import { fromMutations } from 'vuex-aggregate'

const namespace = 'counter'

const mutations = {
  increment(state: State): void {
    state.count++
  },
  setCount(state: State, count: number): void {
    state.count = count
  },
  setName(state: State, name: string): void {
    state.name = name
  }
}

export const { commits, mutationTypes } = fromMutations(mutations, namespace)
```

各module の namespace がコンフリクトしない限り、それを配慮する必要もありません。もちろん、MutationType を import し mutation 関数名とすることも不要です。

namespaced: true ではあれど、modulesはネストせず、ファイル自体もネストせずにフラットに配備。ファイル名をそのまま namespace とすることを想定しています。


## PR / issue お待ちしています

現状では推論の都合上、modules かつ namespaced という縛りがあり、各々敷かれている開発規約に抵触する恐れがあります。なにぶん Vuex 初心者のため、Vuexハードユーザーの皆さまから PR や issue を頂けますと幸いです。

また、筆者は普段 ReactRedux で開発しており、vuex-aggregate は [redux-aggregate](https://www.npmjs.com/package/redux-aggregate) から発案した姉妹プロダクトです。vue ユーザーにも親しみ易い仕上がりになっていますので、是非試してみてください。