
TypeScript の高度な型 ⑤ 条件分岐で得られる確約と、ネストされた型と、部分的な型抽出
条件分岐で得られる確約
型の条件分岐が成立した場合、Indexed Access Types による型参照が可能になります。
次の型を例に、Deep Nest 型の dee-.nest.value の型を抽出してみます。
interface DeepNest { deep: {nest: {value: string}} } interface ShallowNest { shallow: {value: string} } interface Properties { deep: DeepNest shallow: ShallowNest }
DeepDive型は、Properties型に含まれるプロパティのうち、DeepNest型と互換性があるプロパティに限って、
Salvage<T[]K>を返却します。DeepNest型と互換性がないプロパティは、never型が適用されます。
type Salvage<T extends DeepNest> = T['deep']['nest']['value'] type DeepNest<T> ={ [K in typeof T]: T[K] extends DeepNest ? Salvage<T[K]> : never }[keyof T] type X = DeepDive<Properties> // type X = string
T[‘deep’][‘nest’][‘value’] のようなIndexed Access が成立するのは、Salvage型のGenericsが<T extends DeepNest> と指定されており、
Genericsは、DeepNest型と互換性があることが確約されているためです。
T[K] extends DeepNest ? という構文によりT[K]はDeepNest 型と互換性があることが確約されているために、
Salvage<T[K]>の適用が可能になっています。
このように、抽出したい型が深い階層にある場合、改造型を条件分岐に用いることで、推論を深堀できます。
部分的な型抽出
Conditional Types 構文の中のみ利用できる infer シグネチャを用いると、
部分的な型抽出が可能になります。
これが、 Type Inference in Conditional Types と呼ばれる機能です。
組み込み Utility Types である Retrun Type 型と同じ型で、
この挙動を確認してみましょう。
T extends に続く 「(…arg: any[]) => infer U 」が条件となる型です。
これは、「関数型かつ戻り型がある」型であることを表しています。
この「関数型かつ戻り型がある」条件が満たされた場合、
戻り型のU型を返却します。
function greet() { return 'Hello!' } type Return<T> = T extends(...arg: any[] => infer U ? U : never) type R = Return<typeof greet> // type R = string
引数型の抽出
function greet(name: string, age: number){ return 'hello! Im $(name). $(age) years old' } type A1<T> = T extends (...arg: [infer U, ...any[]]) => any ? U : never type A2<T> = T extends (...arg: [any, infer U, ...any[]]) => any ? U : never type AA<T> = T extends (...arg: infer U) => any ? U : never type X = A1<typeof greet> // type X = string type Y = A2<typeof greet> // type Y = number type Z = AA<typeof greet> // type Z = [string, number]
Promise.resolve 引数型の抽出
下記のコードでは、async関数は、X型で確認できることからもわかるように、Promise<String>を返す関数です。Resolve Arg型では、PromiseのGenericsにinfer Uを配置してるために、string型が導かれます。
async function greet(){ return 'Hello!' } type ResolveArg<T> = T extends () => Promise<infer U> ? : never type X = typeof greet // type X = () => Promise<string> type Y = ResolveArg<typeof greet> //type Y = string
Promise型に限らず、このようにGenerics に対して infer シグネチャを適用できます。