LoginSignup
17
10

More than 5 years have passed since last update.

Vuex の Payload を推論型で守る

Last updated at Posted at 2018-07-18

⛑ 型で何を守るのか?

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

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

リリース前 リリース後
依存の分散 考慮漏れエラー
inferred_types_for_flux1.png inferred_types_for_flux2.png

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

vuex-aggregate は commit / disptch の空振りと、それらの Payload を 推論型 で守ることを提供しています。README に記したとおり、まずは 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 ならではの芸です。
> 導出定義はこちら

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 から発案した姉妹プロダクトです。vue ユーザーにも親しみ易い仕上がりになっていますので、是非試してみてください。

17
10
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
17
10