こんにちは!
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公式からコードをいただいてます。
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ですね。
条件型:
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について再度理解を始めました。
条件型:
T extends U ? X : Y
inferとは、の U の部分で使うキーワードです。
inferはTypeScriptの条件型で使われる特殊なキーワード、「条件型」です。
(・・なんだか良く分からない)
「条件型」は、ある型が特定の条件を満たすかどうかをチェックし、その結果によって異なる型を返すことができる機能です。
これはプログラミングのif文のようなものですが、型に対して行います。
そして、inferキーワードはこの「条件型」の中で使われ、特定の型から別の型を「推論」(つまり、取り出す)するために使われます。
例えば、ある関数の戻り値の型を知りたいときに、inferを使うことができます。
また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) オブジェクトであり、関数に渡された引数の値を含んでおり、関数内からアクセスすることができます。
(少し理解してきた。でも実際に使えるのだろうか?)
もう1つサンプルコードを見てみました。
またまた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が3つもあってさっきのより難しい気がする・・)
(いや!よく見たらわかる。)
T extends (infer U)[]
T型が配列型であるかどうかをチェックしている。
配列型であれば、その配列の要素の型をinfer Uで取り出し、その型U型を返す。
T extends (...args: any[]) => infer U
関数型であるかどうかをチェック。
T extends Promise
Promise型であるかどうかをチェック。
string型は配列型でも関数型でもPromise型でもないのは、そのまま型を返す。
だから以下のコードはstringを返す。
type T0 = Unpacked; // 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