普段 Redux を愛用しているのですが、ボイラープレートの生産性の悪さが好きではなく、ヘルパーを作成しました。
紹介記事: Redux はもう辛くない。redux-aggregate
このライブラリですが、以下の様なsrc関数があるとします。
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関数が生成されます。
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
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 の機能は普段のコーディングでは中々利用場面が思い浮かびませんが、
こういったライブラリにはインパクトが大きいですね。今後も変化を追っていきたいと思います。