TypeScriptのConditional Typesの概要と使い方を具体例で理解する

JavaScript

Conditional Types(条件付き型)について

Conditional Typesとは条件分岐の概念がある型のことをいいます。
Conditional TypesはT extends U ? A : Bのように記述し、『型Tが型Uを満たす場合は型A、満たさない場合は型B』となります。

例えば『型Tがstring型を満たす場合はtrue型、満たさない場合はfalse型』をConditional Typesで表現するとT extends string ? true : falseとなります。
具体的な利用方法は以下の通りです。

// IsString<T>型は『型Tがstring型を満たす場合はtrue型、満たさない場合はfalse型』というConditional Types
type IsString<T> = T extends string ? true : false

// 'a'はstring型を満たすのでtrue型
type A = IsString<'a'>
// type A = true

// 1はstring型を満たさないのでfalse型
type B = IsString<1>
// type B = false

// booleanはstring型を満たさないのでfalse型
type C = IsString<boolean>
// type C = false

Conditional Typesの使用例

Conditional Typesの具体例について紹介します。

前提知識: Union型におけるnever型の特性について

Conditonal Typesはnever型と組み合わせることが多いので、never型の特性について紹介します。

never型にはどんな値もセットできない型ですので、T | neverというUnion型はTになります。
具体的には以下の通りです。

type Foo = string | never
// type Foo = string と型推論される

指定した特定の型のみを抽出する

指定した特定の型のみを抽出するには、never型を利用したT extends U ? T : neverというConditional Typesを利用します。
T extends U ? T : neverは『型Tが型Uを満たす場合は型T、満たさない場合はnever型』を表現します。つまり、『型Uを満たさなければ型Tをnever型として扱う』と言い換えられます。
Union型において、型Uを満たさない型Tは除外されることになります。

具体的なコードは以下の通りです。

type Square = {
  kind: "square";
  size: number;
}

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
}

type Triangle = {
  kind: "triangle";
  width: number;
  height: number;
}

type FourSidedShape = Square | Rectangle;

// FourSidedShapeを満たす型のみをフィリタリングする Conditional Types
type FourSidedShapeFilter<T> = T extends FourSidedShape ? T : never

// Triangleは FourSidedShape を満たさないので FourSidedShapeTypeから除外されている
type FourSidedShapeType = FourSidedShapeFilter<Square | Rectangle | Triangle>
// type FourSidedShapeType = Square | Rectangle

type FourSidedShapeType = FourSidedShapeFilter<Square | Rectangle | Triangle>type FourSidedShapeType = Square | Rectangleになる理由について説明します。

FourSidedShapeFilter<Square>SquareFourSidedShapeFilter<Rectangle>RectangleFourSidedShapeFilter<Triangle>neverになります。
ですので、FourSidedShapeFilter<Square | Rectangle | Triangle>Square | Rectangle | neverというUnion型になります。
Union型においてnever型は無視されるので、最終的にSquare | RectangleというUnion型になります。

特定のプロパティを持つオブジェクトのみを取り出す

T extends U ? A : BUには型だけでなくオブジェクトのプロパティを指定できます。

例えば以下のように動物に関するオブジェクトの型を定義したとします。

type Cat = { meows: true };
type Dog = { barks: true };
type Cheetah = { meows: true; fast: true };
type Wolf = { barks: true; howls: true };

このとき、犬科の動物のみを取り出したいと考えます。犬科の定義は{ barks: true }なオブジェクトです。
{ barks: true }なオブジェクトをConditional Typesを利用して取り出す場合はT extends { barks: true } ? T : never;となります。
T extends { barks: true } ? T : never;{ barks: true }を満たさなければ型Tをnever型として扱うので、Union型において型Tは除外されます。

具体的なコードは以下の通りです。
ExtractDogish<Cat>ExtractDogish<Cheetah>はnever型になるため、Dogish型はnever | Dog | never | Wolf、つまりDog | Wolfとなります。

type ExtractDogish<T> = T extends { barks: true } ? T : never;

type Animals = Cat | Dog | Cheetah | Wolf;

type Dogish = ExtractDogish<Animals>;
// type Dogish = Dog | Wolf

指定した特定の型のみを除去する

<T, U> = T extends U ? never : Tは『型Tが第2引数の型Uを満たす場合はnever型、満たさない場合は型T』を表現するConditional Typesです。
このConditional Typesを利用すると、第2引数で指定した型を第1引数のUnion型から除去するということができます。

具体的なコードは以下の通りです。

type Square = {
  kind: "square";
  size: number;
}

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
}

type Triangle = {
  kind: "triangle";
  width: number;
  height: number;
}

type Diff<T, U> = T extends U ? never : T;

type FourSidedShape = Diff<Square | Rectangle | Triangle, Triangle>
// type FourSidedShape = Square | Rectangle

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考