「T
というジェネリック型がIFoo
ならA
、違うならB
」というように与えられた型によって分岐した結果の型を返すことができます。
基本
例えばこれはこのような形です。
/**
* P(arent), C(hild)
*/
type T = C extends P ? U : V;
これはC
がP
の拡張形式ならU
型に、違うならV
型になるという意味になります。
同じプロパティを持ってれば
true
な感じ、なので継承はtrue
class P {}
class C {}
// type T = U
class P {
foo: any;
}
class C extends P {}
// type T = U
class P {
foo: any;
}
class C {}
// type T = V
Generics で複数の型を提供する
if-else-if-else
のように 2 つ以上の条件も設定できます。
これは属すから
true
な感じ
enum StatusCode {
BadRequest = 400,
NotFound = 404,
InternalServerError = 500
}
/** @alias */
type Code = StatusCode;
interface BadRequestResponse {
code: Code.BadRequest;
fields: any[];
}
interface NotFoundResponse {
code: Code.NotFound;
errors: any[];
}
interface InternalServerErrorResponse {
code: Code.InternalServerError;
message: any;
}
type Response = Code.BadRequest extends C
? BadRequestResponse
: Code.NotFound extends C
? NotFoundResponse
: InternalServerErrorResponse;
type A = Response;
// type A = NotFoundResponse
type B = Response;
// type B = InternalServerErrorResponse
Generic Type の再帰はできない
以下のReturnTypeRecursive
は関数の場合は再帰的に結果を辿っていって最終的な結果の型を取得しようと思ったものですが、これは「循環参照してますよ(Type alias 'ReturnTypeRecursive' circularly references itself.ts(2456))」エラーを起こします。
type ReturnTypeRecursive<
Fn extends (...args: any[]) => any
> = ReturnType extends (...args: any[]) => any
? ReturnTypeRecursive
: ReturnType;
Generic じゃないなら再帰できるようです
type AFunction = (...args: any[]) => any | AFunction;
ネストを深くすればそのようなことはできる
ネストが浅いのであれば使用したいケースまでの再帰ライクに定義するといった使い方は可能です。
export type ReturnTypeRecursive = ReturnType<
Fn
> extends AFunction
? ReturnType> extends AFunction
? ReturnType>> extends AFunction
? never
: ReturnType>>
: ReturnType>
: ReturnType;
const aFn = () => () => () => 'foo' as const;
type A = ReturnTypeRecursive;
// type A = "foo"
infer
で一部分の型を取得する
以下は関数の戻り値を取得できます。infer
はパターンマッチみたく、その場の型を条件結果の部分で使うことができます。
type ReturnType = T extends (...args: any[]) => infer F
? F
: never;
type A = ReturnType<() => number>;
// type A = number;
実はReturnType
は最初から定義されてるので実装はいりません。
type A = ReturnType;
例えば、第 2 引数の型が取りたい場合は、引数の型指定部分でinfer
を使います。
// tslint:disable:no-unused
type SecondArg = T extends (
a1: any,
// F が [tslint] TypeParameter 'F' is unused. [no-unused]
// になってしまうので tslint:disable してます
a2: infer F,
...args: any[]
) => any
? F
: never;
// tslint:enable:no-unused
type A = SecondArg<(_a: number, _b: string, _c: boolean) => {}>;
// type A = string