こんにちは!TypeScriptの型定義、毎日お疲れ様です。
「よし、少しずつ型が書けるようになってきたぞ!」と思った矢先、ネットのコードやライブラリの型定義で見かける謎の呪文……それが今回お話しする `infer`(インファー) キーワードです。
`Conditional Types`(条件型:`T extends U ? X : Y` みたいなやつですね)の中でこっそり使われるこの `infer`、初めて見たときは「なんだこれ宇宙語か?」とそっとタブを閉じたくなる気持ち、めちゃくちゃよく分かります。私も昔は画面の前で頭を抱えました。
でも、大丈夫ですよ。安心してください。
今日は難しい専門用語の沼にハマる前に、身近な「お買い物の自動仕分け」に例えて、この `infer` の正体を優しく、かつガッツリと解体していきましょう!
—
そもそも `infer` ってなに?(身近な例え話)
突然ですが、あなたは近所のスーパーで働く凄腕の仕分けスタッフだと想像してください。
毎日、色々な荷物が段ボール箱に入れられて届きます。
中身はさまざま。お菓子かもしれないし、お肉かもしれない、あるいは謎の物体かもしれません。
ここであなたに与えられたミッションはこうです。
「この段ボール箱を開けて、中に入っている『お肉』のパックを取り出し、そのお肉の種類(豚肉なのか牛肉なのか)のラベルを貼ってください」
この「箱の中から、特定のルールで見つけて(推論して)、取り出す」という一連の作業をしてくれる魔法のキーワードが、TypeScriptにおける `infer` なんです。
英語の “infer(推測する・察する)” という言葉の通り、TypeScriptに「型の中から、特定のパーツを推測して捕まえちゃいなよ!」と指示を出すためのものなんですね。
—
基本のキ:関数の「戻り値の型」をこっそりパクる
百聞は一見にしかず。一番よくある実務のシーンを見てみましょう。
例えば、自分で書いた関数なら戻り値の型なんてすぐ分かりますが、どこかから持ってきたサードパーティ製のライブラリの関数だと、「あれ、この関数っていったい何を返すんだっけ?」と迷うことがあります。
そんなとき、`infer` を使えば、「この関数の戻り値の型だけをキレイに抜き取る」 ことができます。
// サンプルとして、ユーザー情報を取得する適当な関数を用意します
const fetchUser = () => {
return { id: 1, name: ‘Taro’, role: ‘admin’ };
};
// 1. まず、渡された型が「関数かどうか」をチェックする準備をします
// 「T が (…args: any[]) => infer R という形に当てはまるなら、その戻り値の型を『R』と名付けてね」という意味です
type MyReturnType
// 2. 実際に `fetchUser` 関数の型を渡してみましょう
type UserType = MyReturnType
// さあ、UserType の中身はどうなっているでしょう?
// 正解は、 { id: number; name: string; role: string; } というオブジェクトの型になります!
ここで登場した `infer R` が、まさに先ほどの仕分けスタッフです。
`T extends (…args: any[]) => infer R` という条件式で、TypeScriptにこう伝えています。
「もし `T` が関数だったら、その戻り値(Return)の型を `R` という名前の変数にインプット(推論)して! そして、その `R` をそのまま返すよ!」
これが `infer` の基本的な仕組みです。どうでしょう、少し霧が晴れてきた感覚はありませんか?
—
つまずきやすいポイント:「どこにでも書けるわけじゃない」
ここで、初心者の人がよくやってしまう「あるあるエラー」についてお話しておきますね。
「よし、便利そうだから普通の型定義のあちこちに `infer` を書いちゃおう!」とすると、TypeScriptのコンパイラから冷たく怒られます。
> 「Error: ‘infer’ declarations are only permitted in the ‘extends’ clause of a conditional type.(infer は条件型の extends の中だけでしか使えませんよ!)」
そうなんです。`infer` は条件型(Conditional Types)の `extends` の中でしか生きることができない、ちょっぴりシャイなキーワードです。
必ず、
`type 何か
という形(型における `if` 文のようなもの)のセットの中で使う、というルールだけ覚えておいてくださいね。
—
実務で使える!ちょっとクールな `infer` 活用パターン
基本が分かったところで、もう少し実務の現場で「おっ、こいつ分かってるな!」と思われるような、配列の中身を取り出す例を見てみましょう。
例えば、`Promise` の中身(解決された値の型)を取り出したいときや、配列の要素の型だけを取り出したいときです。
// 配列の要素の型だけを取り出すカスタム型
// 「もし T が何らかの配列 (infer U[]) なら、その要素の型 U を返す。違うなら never」
type Flatten
// 試してみましょう
type StringArray = string[];
type ExtractedString = Flatten
// 結果: string (配列の枠が外れて、中身の string だけが手に入ります!)
type NumberItem = number;
type NotAnArray = Flatten
// 結果: number (配列じゃなければ、そのままの型が返ってきます)
これ、実務でAPIのレスポンス型をいじるときや、既存の複雑な型をスッキリ整形したいときにめちゃくちゃ重宝します。一から手書きで型を書き直さなくていいので、コードのメンテナンス性が爆上がりするんです。
—
まとめ:`infer` は怖くない!
ここまでお付き合いいただきありがとうございます。どうでしたか?
`infer` を一言でまとめると、こうなります。
> 「複雑な型という段ボール箱から、自分が欲しいパーツをこっそり引き抜いてくれる、優秀な自動仕分けシステム」
最初は呪文のように見えても、分解して役割を知ってあげれば、あなたのTypeScriptライフを強力に支えてくれる頼もしい相棒になります。
もし実際のコードで「ここ、どうやって型を抽出したらいいんだろ…」と悩んだときは、今日お話しした「条件型と `infer` のコンボ」を思い出してみてください。
あなたのフロントエンド開発が、少しでも楽しく、心地よいものになりますように。
それではまた次の記事でお会いしましょう!

コメント