📚

Vuexの型定義でモジュールでの型解決してくれるようにしてみた

2021/09/11に公開

前提

Nuxt.jsでVuexを使っているのでそのときに
https://github.com/ktsn/vuex-type-helper
以下を利用させてもらっていました

ただ、モジュールのstore場合利用時にtypeがうまくはまらないから、どうするんだろうとか色々見てたのですがあんまりいい手段が見つからなく、自分で型定義でテンプレートリテラル部分書いたらどうなんだろうとおもってやってみました。
正直もっと良い手段があると思いますが、今回は自分の勉強踏まえの備忘録。
そして、多分Vue3対応とかが入ったらちゃんと動いていくんだと思うので、後で書き換えればいいし、現状型の問題だけなので一旦、パッチ的なやり方でいいやという気持ち。

参考+変更してみたのは、上記のレポジトリの以下の部分です

export type Dispatcher<
  Actions,
  M extends Mapper<Actions> = Mapper<Actions>,
  K extends keyof M = keyof M
> = M[K]

個人的な結論

とりあえずモジュール名つければいいやん?(安直)って思ったので、型の指定のときにつけるようにしました。

type PayloadForWithType<T> = T extends void ? {} : T

type ModuleMapper<P, M extends string> = {
  [K in keyof P & string]: {
    type: `${M}/${K}`
  } & PayloadForWithType<P[K]>
}

export type ModuleDispatcher<
  Actions,
  NameSpace extends string,
  M extends ModuleKeyMapper<Actions, NameSpace> = ModuleKeyMapper<
    Actions,
    NameSpace
  >,
  K extends keyof M = keyof M,
> = M[K]

ここで面白いのが、TypeScriptのkeyを作る際に型がstringの場合は結合ができるという点です。${M}/${K} の部分。でもって、& string ってすることで、無理やりkeyofのやつをstringにできる。
(ここ1年の最近入ったっぽい)
https://github.com/microsoft/TypeScript/pull/40336
サンプルでもありますが以下の部分が該当します(keyにaとかbってのがついてる)

type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
type T70 = DoubleProp<{ a: string, b: number }>;  // { a1: string, a2: string, b1: number, b2: number }

利用部分

個人的には何回も書くのは面倒なので、以下の形でexport できるようにしています

export type UserModule = 'user'
export type UserDispatcher = ModuleDispatcher<
  UserActions, // ここはvuex-type-helperのREADMEの通り作ってくだされ
  UserModule
>
this.$store.dispatch<UserDispatcher>({
    type: 'user/load',
    uid: this.uid,
})

Gettersも作ってみた

一旦作ってしまえればなるほど?の理解で進められます。

type ModuleGetterMapper<P, M extends string> = {
  [K in keyof P & string as `${M}/${K}`]: P[K]
}

でもって何回も書くのは面倒だから以下のように作って

export type UserGetter = ModuleGetterMapper<
  UserGetters, // ここもvuex-type-helperの以下略
  UserModule
>

このように使う感じで一旦落ち着いてます。正直すごいいい感じって言うわけじゃないです。(補完はされるし良いかぐらい)

(this.$store.getters as UserGetter)[
  'user/getName'
](this.uid)

まとめ

単純にNamespace入れればいいやんとか考える前に色々試行錯誤してたんですが、こういう型パズルについて思いを馳せると頭の体操になると思いましたので、たまにはやってみると良いかもです

正直ModulenName周り書いたら、実際に使うときは名前だけで行けるようにしてもいいと思うのだが、どうせVue3あたりできちんとVuex周りに修正入るだろうしいいやーっていう気持ち(2回目
たぶんinfer辺使ってどのDispatcherを使うかを決定するような感じにするとよしなにできる気もする。

Discussion