inferが最近使えるようになってきた話

こんにちは！
TSKaigi の運営チームの添田です。

この記事はTypeScript Advent Calendar 2023の22日目の記事です！
https://qiita.com/advent-calendar/2023/typescript
​
※TSKaigi とは、現在企画中の TypeScript の大規模カンファレンスで、
運営チームはTypeScript好きのエンジニア達で結成されている素敵なチームです。
​

## この記事について

inferが突然出てきて戸惑い、コツを掴めずに理解に時間がかかった経験がありました。
そんなinferについて自分なりにまとめた記事となります。
​

## inferを理解するためにやったこと

### Conditional Typeについて復習
まずinferを理解するにあたり、
ベーシックなConditional Typeについて復習をしました。
以下は[TypeScript公式](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)からコードをいただいてます。

```typescript
interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
​
type Example1 = Dog extends Animal ? number : string;
type Example2 = RegExp extends Animal ? number : string;
```

この**三項演算子**みたいなのがConditional Typeですね。
​
条件型：
```typescript
T extends U ? X : Y
```

- T extends U　は、T型がU型のサブタイプ（つまり、U型に代入可能）であるかどうかをチェックします。
- ? X : Yは、T extends UがtrueであればX型を返し、そうでなければY型を返します。
​

つまり、T型がU型に含まれている時に、X型を返し、含まれなければY型を返します。

### Conditional TypeのUの部分に注目
先ほどのConditional Typeを理解した上で、inferについて再度理解を始めました。
​
条件型：
```typescript
T extends U ? X : Y
```
inferとは、の **U の部分で使うキーワード**です。
inferはTypeScriptの条件型で使われる特殊なキーワード、「条件型」です。
​
（・・なんだか良く分からない）
​
「条件型」は、ある型が特定の条件を満たすかどうかをチェックし、その結果によって異なる型を返すことができる機能です。
これはプログラミングのif文のようなものですが、型に対して行います。
そして、inferキーワードはこの「条件型」の中で使われ、特定の型から別の型を「推論」（つまり、取り出す）するために使われます。
​
例えば、ある関数の戻り値の型を知りたいときに、inferを使うことができます。
​

また[TypeScript公式](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html)のコードを見てみました。
```typescript
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;
​
type Num = GetReturnType<() => number>;
​
type Str = GetReturnType<(x: string) => string>;
​
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
```

確かに
U の所が (...args: never[]) => infer Return になっている。
​
上記は、Type（T）が「（引数を取らない）関数」であるかどうかをチェックしています。

もしTypeが関数であれば、その関数の戻り値の型を infer Return で取り出し、その型 Return を返します。
もしTypeが関数でなければ、never型を返します。

ちなみに引数を取る関数の場合は、...args: any[] となります。
​
補足ですが、
...args は arguments オブジェクトでJavaScriptでも使われています。
>https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/arguments
arguments は配列風 (Array-like) オブジェクトであり、関数に渡された引数の値を含んでおり、関数内からアクセスすることができます。


（少し理解してきた。でも実際に使えるのだろうか？）
​

もう１つサンプルコードを見てみました。
​
またまた[TypeScript公式](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types)のコードを見て確認をしていきました。​
```typescript
type Unpacked<T> = T extends (infer U)[]
  ? U
  : T extends (...args: any[]) => infer U
  ? U
  : T extends Promise<infer U>
  ? U
  : T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
```
（くぅ、、、inferが３つもあってさっきのより難しい気がする・・）

（いや！よく見たらわかる。）
​
T extends (infer U)[]
T型が配列型であるかどうかをチェックしている。
配列型であれば、その配列の要素の型をinfer Uで取り出し、その型U型を返す。
​
T extends (...args: any[]) => infer U
関数型であるかどうかをチェック。
​
T extends Promise<infer U>
Promise型であるかどうかをチェック。
​
string型は配列型でも関数型でもPromise型でもないのは、そのまま型を返す。
だから以下のコードはstringを返す。
type T0 = Unpacked<string>; // string
​
（少し利点も分かってきて使えそうな気がしてきた！）
​
​
以上となります。

​
TypeScriptは勉強すればする程難しくなってきて、奥が深くとっても楽しいと思ってます！
日々綺麗なTypeScriptコードを学んで、もっともっと使いこなせる様になっていきたいと思ってます！
​
読んでくださりありがとうございました。

よいクリスマスを！
❄ Merry*❄ 。 • ˚ ˚ ˛ ˚ ˛ • ❄
•。★❄ Christmas❄ 。* 。❄
° 。 ° ˛˚˛ *    _Π_____*。*˚
˚ ˛❄ •˛•˚ */______/~＼。˚
˚. •˛• ❄˚ ｜  田  ｜門｜ ˚
⌒⌒⌒⌒⌒⌒⌒⌒ ⌒⌒⌒ ⌒
​
​
▼参考にさせていただいた神記事
https://zenn.dev/oreo2990/articles/1040312d7af066
https://qiita.com/iwata-goq/items/677251ce778afcacd33e
​
▼公式
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html