TypeScript

Conditional Types

基本

このような形です。

/**
 * P(arent), C(hild)
 */
type T<U, V> = C extends P ? U : V;

これはCPの拡張形式なら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<C extends Code> = Code.BadRequest extends C
  ? BadRequestResponse
  : Code.NotFound extends C
  ? NotFoundResponse
  : InternalServerErrorResponse;

type A = Response<Code.NotFound>;
// type A = NotFoundResponse
type B = Response<Code.InternalServerError>;
// type B = InternalServerErrorResponse

infer で一部分の型を取得する

以下は関数の戻り値を取得できます。inferはパターンマッチみたく、その場の型を条件結果の部分で使うことができます。

type ReturnType<T extends Function> = T extends (...args: any[]) => infer F
  ? F
  : never;
type A = ReturnType<() => number>;
// type A = number;

実はReturnTypeは最初から定義されてるので実装はいりません。

type A = ReturnType<Fn>;

例えば、第 2 引数の型が取りたい場合は、引数の型指定部分でinferを使います。

// tslint:disable:no-unused
type SecondArg<T extends Function> = 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