Edited at

TypeScript2.8 Conditional Types 活用事例

More than 1 year has passed since last update.

普段 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 の機能は普段のコーディングでは中々利用場面が思い浮かびませんが、

こういったライブラリにはインパクトが大きいですね。今後も変化を追っていきたいと思います。