2026年03月28日
こんにちは! 今日もプログラミングを楽しんでいますか?
こんかいは趣向を変えて、いつも扱っている JavaScript や CSS、HTML ではなく、TypeScript についてのお話です。
さて、TypeScript といえば「型パズル」と通称される、ギミック入りの型定義記法=型関数が有名です。そのギミックのひとつ、型演算子 infer をご存知でしょうか?
名前くらいは聞いたことはある、なんだかよくわからないけど、書いたら動いた……という方もいらっしゃるのではないでしょうか(ちなみに、英単語の “infer” とは「推論する」という意味です)。中には、難しすぎて考えたくもない、という方もいるかも知れません。
こんかいは、言葉の意味があまりにも大ざっぱなゆえに、実際の挙動がイメージしにくいこの型演算子inferに対する嫌悪感を、さくっと解決しましょう、という記事となります。
TL;DR
条件型の中で、「今はまだ分からない型」をいったん据え置き、あとで型が確定したときに、その中身を名前付きで取り出すための目印がinferです。
定義時にではなく、使用時に初めてわかる型がある
例として、ユーザーのデータと、施設のデータを扱うコードを考えてみましょう。まずは簡易的に、それぞれを型宣言してみます。
/** ユーザー */
type User = {
/** 一意のユーザー ID */
id: number;
/** ユーザーの氏名 */
name: string;
};
/** 施設 */
type Facility = {
/** 一意の施設 ID */
id: number;
/** 施設の住所 */
address: string;
};これらは一覧として受け取るので、そのための型を Array (配列)として作ってしまいましょう。
/** ユーザー一覧 */
type Users = User[];
/** 施設一覧 */
type Facilities = Facility[];さて、これらは「一覧取得 REST API」により取得されるとします。通信成功時のレスポンスは、それぞれこんなイメージです。
/** 通信成功。ジェネリクス T は Users か Facilities 型を実行時に与える */
type SuccessFetch<T> = {
/** 通信成功 */
success: true;
/** 取得結果 */
data: T;
};キー data には、対象となるデータが、Users または Facilities として存在しています。
一方、画面描画の実装は、対象がユーザーなのか施設なのかによって、表示すべきものが異なります。なので、Users または Facilities から、User 型の配列なのか Facility 型のデータの配列なのかを抽出して、各プロパティの値を表示できないといけません。
それでは、Array からその要素の型を抽出するユーティリティー型を自作してみましょう。こんなイメージです。
/** T には、 Array である Users または Facilities を与えます */
type ElementType<T> = // ええっと…どうなるの?さあ困りました。「Array なのはわかる。でも、その要素の型をどう書くべきか?」……やりたいことは単に、
/** 要素の型 を E という名前で得たいだけなのに! */
type ElementType<T> = T extends Array<E> ? E : never;であるはずなのに、このコードでは「型 E は定義されていません」というエラーとなってしまいます。
ならば、いっそのこと E も与えてしまえばいいのでしょうか?
/** E が分かってるなら、抽出する意味はない! */
type ElementType<T, E> = T extends Array<E> ? E : never;E にあたる型がわからないから抽出したいのに、これでは E が分かっている前提のコードになってしまいます。これでは、抽出のユーティリティー型とは呼べません。
なにかいいアイデアはないものでしょうか?
infer = 型の評価の据え置き
上の例でエラーになった原因は結局のところ、このユーティリティー型の定義時に E という型が存在しないためです。でも使用時には extends による条件型によって、型が判明する。つまり必要だったのは「定義時ではなく、使用時まで E の評価を保留する=型の評価の据え置き」なのです。
そしてそのための型演算子こそが、inferなのです!
というわけで、作ってみますね。
/**
* `infer E` と書くと、
* 「E がどんな型なのかの評価は、T が何らかの Array だと決定するまで、据え置かないといけないんだな」
* と理解してくれる!
*/
type ElementType<T> = T extends Array<infer E> ? E : never;これでOK! では、このユーティリティー型をじっさいに試してみましょう。
type TheElement = ElementType<Users>;
/*
type TheElement = {
id: number;
name: string;
}
*/type TheElement = ElementType<Facilities>;
/*
type TheElement = {
id: number;
address: string;
}
*/みごと! 配列からその要素の型を抽出することができました!
infer は、
「ここに来る型はあとで決まるので、そのときに取り出せるように名前を付けておく」という目印
の意味が、おわかりいただけたと思います。
では、ちょっと意地悪な操作をしてみましょう。
type ElementType<T> = T extends Array<E> ? E : never;
type TheElement = ElementType<any>; // TheElement は、どんな型になる?これを読んだ TypeScript のパーサは、このように考えます。
anyは何でもいい型なので、「Arrayかもしれないし、そうでないかもしれない」。- つまり
extends判定は「成功とも失敗とも言える」状態になる……。両方の場合を考慮しなきゃいけないな。- 成功した場合、これは
Arrayの要素の型の指定がないだけといえるから、Array<any>とみなしてEは<>の中に入る型、つまりanyとなる。 - 逆にそもそもこれが
Arrayではないなら、判定は失敗だ。この場合はneverとなる。
- 成功した場合、これは
- ええと、結果は
anyでもありneverでもある……??? - 仕方ない、ここは安全を取って
unknownって評価しちゃえ!(anyは特殊な挙動をするため、このような結果になります。詳細はここでは割愛) - 「
TheElementは、unknownです!」
結果、TheElement は unknown と評価されます。
もう少し正確な説明
Web 上では infer の説明として:
- 条件型のパターンマッチの中で型変数を導入する構文です。
- 条件型の一部を切り出して新しい型変数とし、元の型変数をその型に束縛する構文です。
のように紹介している文書を多く見かけます。たしかにこれは説明としては正確なのですが、そもそも infer がわからない方が、上記の説明を理解できるのでしょうか?
そこで、もう少し「メンタルモデルとして」わかりやすい説明を試みてみます。
type ElementType<T> = T extends Array<infer E> ? E : never;とは、
- 「
Tのextends先が『Eの配列である』とみなせるとき、ElementType<T>はEに解決される。さもなくばnever(型なし)に解決される」という意味です。 - 言い換えると、
Tが『何らかの型の配列』かどうか分からないうちは、ElementType<T>の解決先がEなのかneverなのかが決まりません。 - これを解決するべく、「
extendsによるTの検証が終わるまで、評価を保留しなければならない型がある場合、評価された結果の型をEとして扱う」というルールができました。その「評価を保留せよ」という目印こそがinferです。 - つまりこの
inferは、
「Tがextendsによる『何らかの型の配列であるか?』の検証を終えたら、保留していた『この位置に置かれるべき型』を、Eという型名で使用します」
という意味になります。 - 上記の理由から、
inferは条件型のextendsの右辺、つまりその判定に使う型でのみ使えます(型の「形状を照合する場所」が、この右辺だからです)。
……イメージが、なんとなくでも掴めたでしょうか?
関数と相性バツグン!
評価の据え置きと聞いて、「もしや infer って、関数と相性がいいのでは?」と気づいた方もいらっしゃると思います。関数は「実行されるまで中身が使われない」という意味で、「あとで決まる」という点が共通していますよね。
というわけで、次の例を見てみましょう。
/** その関数の引数を、タプルとして得る */
type UnwrapArg<T> =
T extends (...arg: infer A) => any // 関数型オブジェクトも置ける
? A // 関数実行時の引数の型を、 A というタプルだと表現できる場合、その A を返す
: never; // でなければ never。関数の実装から、その関数の引数をタプルで得る、なんてことができてしまいます!
function noParams() {
return "引数がない";
}
type NoParams = UnwrapArg<typeof noParams>;
/*
type NoParams = []
*/
function oneParam(p1: number) {
return p1 * 2;
}
type OneParam = UnwrapArg<typeof oneParam>;
/*
type OneParam = [p1: number]
*/
function twoParams(p1: number, p2: string) {
return p1 + p2;
}
type TwoParams = UnwrapArg<typeof twoParams>;
/*
type TwoParams = [p1: number, p2: string]
*/アイデア次第で、型パズルの可能性が大きく広がりそうですね!
タプルの先頭を得てみよう!
次は、それこそ何が来るのか本当にわからない、タプル型の先頭の要素の型を、infer を利用して取得してみましょう。これがそのユーティリティー型です。
type Head<T> =
T extends [infer H, ...any[]] ? H : never;シンプルですが、まるで魔法の呪文みたいですね。これで本当にタプルの先頭の型を得られるのでしょうか? 試してみましょう。
type First = Head<[1, 2, 3]>;
/*
type First = 1
*/
type Param1 = Head<[url: string | URL, method: "get" | "post", options?: any]>;
/*
type Param1 = string | URL
*/タプルの先頭の型が、間違いなく得られています! でも、なぜあの定義で、先頭の型を得られるのでしょうか?
型を分解し、指定した中身を取り出す=構造分解
じつは、タプルから先頭の要素の型を取り出すこのユーティリティー型の定義:
T extends [infer H, ...any[]] ? H : never;を、Rust のパターンマッチ風の擬似コードで記述すると、このように表現できます(あくまで擬似コードであり、値ではなく型を記述しています)。
match T {
[H, ..] => H,
_ => never,
}
Tを構成する要素として、この表現でHを当てはめうるなら、呼び出し元の型にHを束縛せよ。さもなくば『型なし』を束縛せよ。
つまり、T の構造を、指定された形状に分解しうるかどうかの判定(構造分解)を行ない、分解できたならその型を返す(束縛する)ということを行なっていたわけです。これが、Head<T> の「魔法の呪文」の正体です。
Head<T> と同様に、UnwrapArg<T> の、
T extends (...arg: infer A) => any ? A : never;も、Rust のパターンマッチ風擬似コードで表せば、
match T {
Fn(..arg: A) -> _ => A,
_ => never,
}となります。
この「パターンマッチ」のメンタルモデルで解釈すれば、
infer Eとは、「この位置にある型をEという名前で受け取る」という意味である。
ことが、ご理解いただけたかと思います。
まとめ
型演算子 infer は、「推論する」という言葉から連想される挙動を想像しにくいせいか、Web 上で様々な解説を見かけます。やはりそれだけ、理解しにくいものと受け止められているのでしょう。
こんかいの記事では、infer の挙動を、主に「評価を据え置きたい型名に対する目印」というメンタルモデルで解説してみました。この奇妙ながら便利な型演算子の働きを、少しでもイメージしやすくなっていたら幸いです。
/** おさらいです! */
// 配列
T extends (infer E)[]
// 関数の引数
T extends (...args: infer A) => any
// 関数戻り値
T extends (...args: any) => infer R
// タプル
T extends [infer H, ...infer Rest]こんかいの記事は、以上です。それでは、良きコーディングライフを!