LoginSignup
9
9

More than 5 years have passed since last update.

TypeScript2.8 Conditional Types 活用事例

Last updated at Posted at 2018-06-20

普段 Redux を愛用しているのですが、ボイラープレートの生産性の悪さが好きではなく、ヘルパーを作成しました。

紹介記事: Redux はもう辛くない。redux-aggregate

このライブラリですが、以下の様なsrc関数があるとします。

src.ts
interface State {
  count: number
}
function increment(state: State): State {
  return { ...state, count: state.count + 1 }
}
function setCount(state: State, payload: { value: number }): State {
  return { ...state, count: payload.value }
}

ここから、以下の様な dist関数が生成されます。

dist.ts
function increment(): { type: string } {
  return { type: 'counter/increment' }
}
function setCount<PL>(payload: PL): { type: string, payload: PL } {
  return { type: 'counter/setCount', payload }
}

dist関数の setCount payload型 に推論をマッピングすることが本稿の主旨です。src関数の第二引数型を抽出、dist関数の第一引数型へ cast します。もし src関数の第二引数が無ければ、dist関数の第一引数は never に。

Conditional Types

v2.8 から追加された機能です。詳細は先人の資料をご参照ください。

初見ではこの Conditional Types の使い道が思いつかなかったのですが、@Quramy さんの記事で、まさに「第二引数を抽出してマッピングする」という節があり、参考にさせていただきました。変更後の型定義は以下の様になりました。
https://github.com/takefumi-yoshii/redux-aggregate/blob/master/typings/index.d.ts#L3-L10

index.ts
type A1<T>          = T extends (a1: infer I, ...rest: any[]) => any ? I : never
type A2<T>          = T extends (a1: any, a2: infer I, ...rest: any[]) => any ? I : never
type MT<T>          = (state: A1<T>) => A1<T>
type MTPL<T>        = (state: A1<T>, payload: A2<T>) => A1<T>
type CR<T>          = () => { type: string }
type CRPL<T>        = (payload: A2<T>) => { type: string, payload: A2<T> }
type Mutation<T>    = MT<T> | MTPL<T>
type Creator<T>     = T extends MT<T> ? CR<T> : CRPL<T>

新しい TypeScript の機能は普段のコーディングでは中々利用場面が思い浮かびませんが、
こういったライブラリにはインパクトが大きいですね。今後も変化を追っていきたいと思います。

9
9
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
9
9