【TypeScript】条件型について

今回は、TypeScriptの条件型についてまとめます。

# 条件型とは
TypeScriptの条件型とは、型システムに条件分岐を導入するものです。

下記の構文で表現され、
「`T` が `U` に割り当て可能であれば `X`、そうでなければ `Y` 」という意味になります。

```ts
T extends U ? X : Y
```

<br />

例えば下記の場合、`T` に入る値が `string` の場合に `true`、そうでない場合に `false` を返します。

```ts
type IsString<T> = T extends string ? true : false;

type Result1 = IsString<"hello">; // true
type Result2 = IsString<123>; // false
```

# 分配条件型
分配条件型は、`T` がユニオン型である場合に、ユニオン型の各要素に対して条件型が個別に適用される仕組みです、

```ts: 
type Distributive<T> = T extends string ? "String" : "Other";

type Result = Distributive<"hello" | 42 | true>;
// Result = "Other" | "String"
```

<br />

ユニオン型から特定の型だけを抽出することもできます。
```ts
type ExtractString<T> = T extends string ? T : never;
type OnlyStrings = ExtractString<"hello" | 42 | "world">;
// OnlyStrings = "hello" | "world"
```

# Inferキーワード
条件型には、ジェネリック型をインラインで宣言する独自構文があり、それが `infer` です。

下記の構文で使用され、
`T` が `Array` 型に一致する場合、`infer U` を使用して配列の要素型を `U` として推論しています。
それ以外の場合は `never` が返されます。

```ts
type InferExample<T> = T extends Array<infer U> ? U : never;
```

<br />

下記の例では配列の要素型を抽出しています。
```ts
type ElementType<T> = T extends Array<infer U> ? U : never;

type StringArray = ElementType<string[]>; // StringArray = string
type NumberArray = ElementType<number[]>; // NumberArray = number
type NonArray = ElementType<boolean>;    // NonArray = never
```

<br />

関数の引数型や戻り値の型を抽出することもできます。

```ts
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ArgumentType<T> = T extends (arg: infer A) => any ? A : never;

type MyFunction = (x: number) => string;

type Result = ReturnType<MyFunction>; // Result = string
type Arg = ArgumentType<MyFunction>; // Arg = number
```

# 組み込みの条件型
TypeScriptには、型を操作するための便利な組み込みユーティリティ型がいくつか用意されています。

## Exclude\<T, U>
`Exclude<T, U>` は、`T` には含まれているが `U` には含まれていない型を計算します。

```ts: 構文
type Exclude<T, U> = T extends U ? never : T;
```

```ts: 例
type Union = "a" | "b" | "c";
type WithoutA = Exclude<Union, "a">;
// WithoutA = "b" | "c"
```

## Extract\<T, U>
`Extract<T, U>` は、`T` には含まれている型のうち、`U` に割り当てできるものを計算します。

```ts: 構文
type Extract<T, U> = T extends U ? T : never;
```

```ts: 例
type Union = "a" | "b" | "c";
type OnlyA = Extract<Union, "a">;
// OnlyA = "a"
```

## NonNullable\<T>
`NonNullable<T>` は、`null` や `undefined` を型から除外するために使用します。

```ts: 構文
type NonNullable<T> = T extends null | undefined ? never : T;
```

```ts: 例
type NullableType = string | null | undefined;
type NonNull = NonNullable<NullableType>;
// NonNull = string
```

## ReturnType\<F>
`ReturnType<F>` は、関数の戻り値の型を計算します。
`F` は型を取得したい関数を表しています。

```ts: 構文
type ReturnType<F> = F extends (...args: any[]) => infer R ? R : any;
```

```ts: 例
function getUser() {
  return { name: "John", age: 30 };
}
type User = ReturnType<typeof getUser>;
// User = { name: string; age: number }
```

## InstanceType\<C>
`InstanceType<C>` は、クラスのインスタンス型を計算します。
`C` は型を取得したいクラス名を表しています。

```ts: 構文
type InstanceType<C extends new (...args: any) => any> = C extends new (...args: any) => infer R ? R : never;
```

```ts: 例
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

type PersonInstance = InstanceType<typeof Person>;
// PersonInstance = Person
```

# おわりに
条件型をしようすることで柔軟にカスタマイズでき、
コードも簡潔に記述できるので非常に便利だと思いました。

それでは。

# 参考文献
https://www.oreilly.co.jp//books/9784873119045/

https://marsquai.com/a70497b9-805e-40a9-855d-1826345ca65f/1dc3824a-2ab9-471f-ad58-6226a37245ce/26594921-303c-447c-9d74-ad2683c8a0c4/

https://numb86-tech.hatenablog.com/entry/2020/07/02/103544