はじめに
TypeScriptを使って開発を進めていく中で分からないことを調べていると時折「infer」見かけます。 気になりでサバイバルTypeScriptを覗いてみると「便利なんだな~」とは思うものの、中身についてはほとんど理解していませんでした。
加えてTypeScriptでの開発もある程度経験を積み理解してきたので、これを機に向き合っていこうと思います。
inferとは?
まずサバイバルTypeScriptでinferについての記載をみると
「inferはConditional Typesの中で使われる型演算子です。inferは「推論する」という意味でextendsの右辺にのみ書くことができます。」
と記載されています。
T extends string ? true : false;
そもそも"Conditional Types"とは何か?についてですが、👆extends以降の部分のことです。
初見では記載されている文からinferはConditional Typesの応用という認識だけして実践はしていませんでした。
ちなみにjsの経験があった私は"Conditional Types"を見た時に
「extends?継承の話じゃないの?」
という考えが中々拭いきれずコードを読んでいる時"extends"を見るたびに「何を継承しているんだろう」と思い全くどういう処理をしているか読めず沼にハマることを繰り返していました。
とりあえず例を見て理解してみる
サバイバルTypeScriptではユーティリティ型ReturnTypeの例から知るとあり、下記のコードが記載されています。
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
お試しの使用例
const request = (url: string): Promise<string> => {
return fetch(url).then((res) => res.text());
};
type X = ReturnType<typeof request>;
「んーよくわからん!」というのが最初の感想です。
冷静になって紐解いてみた
type ReturnType<T extends (...args:any) => any>
/*
(...args: any) 任意の引数を渡せる(残余引数)ことができ、anyなので任意の型で引数を渡すことができる
*/
ReturnTypeに対して任意の関数を渡し、その渡されたものに対して何かしら判定している?というのはパッと見で理解できました。
次に肝心な判定箇所を見ていきます。
= T extends (
...args: any
) => infer R
? R
: any;
色々調べた結果inferは判定文てきなイメージかなと理解したのでその考えで見ていきます。
今回でいう判定箇所とは
T extends (...args:any) => infer R
の部分です。
型に対するextendsとは制約のような意味なのでこの判定は 「Tは関数か?」 という意味になり、関数であれば渡された関数の戻り値の型を返し、そうでなければanyを返すとなります。
ここで使用例を見直します。
const request = (url: string): Promise<string> => {
return fetch(url).then((res) => res.text());
};
type X = ReturnType<typeof request>;
このrequestという関数はurlというstirngの引数を受け取り、Promiseの値を返す関数です。
つまり、先ほどの例に当てはめると
type ReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R /*T は関数ですか?*/
? R /* requestは関数なのでrequestの返却値の型 Promise<string> を返す*/
: any;
となります。
例を読んでみた結果
inferについて6割ぐらいは理解できたのではないかと思う。
もっと複雑なものなのかと思っていたが、冷静になると以外にも理解できた。
今回の内容は基本の範疇だとは思うが、とりあえずちょっとした汎用的な型は作れそうな気がしてきたのでinferが頭になじんだらより深い知識を追い求めようと思います。