Conditional Type

Tというジェネリック型がIFooならA、違うならB」というように与えられた型によって分岐した結果の型を返すことができます。

基本

例えばこれはこのような形です。

/**
 * 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

Generic Type の再帰はできない

以下のReturnTypeRecursiveは関数の場合は再帰的に結果を辿っていって最終的な結果の型を取得しようと思ったものですが、これは「循環参照してますよ(Type alias 'ReturnTypeRecursive' circularly references itself.ts(2456))」エラーを起こします。

type ReturnTypeRecursive<
  Fn extends (...args: any[]) => any
> = ReturnType<Fn> extends (...args: any[]) => any
  ? ReturnTypeRecursive<Fn>
  : ReturnType<Fn>;

Generic じゃないなら再帰できるようです

type AFunction = (...args: any[]) => any | AFunction;

ネストを深くすればそのようなことはできる

ネストが浅いのであれば使用したいケースまでの再帰ライクに定義するといった使い方は可能です。

export type ReturnTypeRecursive<Fn extends AFunction> = ReturnType<
  Fn
> extends AFunction
  ? ReturnType<ReturnType<Fn>> extends AFunction
    ? ReturnType<ReturnType<ReturnType<Fn>>> extends AFunction
      ? never
      : ReturnType<ReturnType<ReturnType<Fn>>>
    : ReturnType<ReturnType<Fn>>
  : ReturnType<Fn>;
  
const aFn = () => () => () => 'foo' as const;
type A = ReturnTypeRecursive<typeof aFn>;
// type A = "foo"

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