1配列から要素の型を取り出す
infer の最も素朴な使い方です。T extends (infer E)[] は「T が何かの配列なら、その要素の型を E と名付けて捕まえる」という意味で、true 側で E を返せます。配列でなかった場合は never を返すのが定番です。
// T が配列なら要素の型を E として取り出す。配列でなければ never
type ArrayItem<T> = T extends (infer E)[] ? E : never;
// number[] から number が取り出せる
const topScore: ArrayItem<number[]> = 98;
// string[] から string が取り出せる
const memberName: ArrayItem<string[]> = "Taro";
console.log(topScore);
console.log(memberName);
2Promise の中身の型を取り出す
ジェネリック型の型引数の位置にも infer を書けます。Promise<infer V> とすれば非同期処理の結果の型だけが取り出せます。false 側に T を返しておけば「Promise ならむいて、そうでなければそのまま」という柔軟な型になります。
// Promise なら中身の型 V を取り出し、そうでなければそのまま返す
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
// Promise<number> から number が取り出せる
const loadedCount: UnwrapPromise<Promise<number>> = 42;
console.log(loadedCount);
// Promise<string[]> のように中身が複雑でも同じ
const loadedNames: UnwrapPromise<Promise<string[]>> = ["Taro", "Hanako"];
console.log(loadedNames);
// Promise でなければそのままの型
const plainText: UnwrapPromise<string> = "そのまま";
console.log(plainText);
3関数の引数の型を取り出す
関数型の引数の位置に infer を書くと、第1引数の型だけを取り出せます。既存の関数に合わせて変数や別の関数の型を決めたいとき、引数の型を手書きでコピーせずに済みます。infer は「型のパターンマッチの捕獲変数」だという感覚がつかめる例です。
// 関数型の第1引数の型を A として取り出す
type FirstArg<F> = F extends (firstParam: infer A, ...rest: any[]) => any ? A : never;
function registerUser(userName: string, age: number): string {
return userName + "(" + age + "歳)を登録しました";
}
// registerUser の第1引数の型 = string が取り出せる
type RegisterName = FirstArg<typeof registerUser>;
const newUserName: RegisterName = "Hanako";
console.log(registerUser(newUserName, 30));
4オブジェクトの特定プロパティの型を取り出す
extends の右側のオブジェクト型の中にも infer を書けます。{ data: infer D } とすれば「data プロパティを持つ型なら、その型を D として捕まえる」という意味になります。どんな形のレスポンスでも data の中身だけ取り出す、API まわりで便利なパターンです。
// data プロパティの型だけを取り出す
type DataOf<T> = T extends { data: infer D } ? D : never;
type UserResponse = { ok: boolean; data: { userName: string; age: number } };
type ListResponse = { ok: boolean; data: string[] };
// それぞれの data の型が取り出せる
const userData: DataOf<UserResponse> = { userName: "Taro", age: 28 };
const listData: DataOf<ListResponse> = ["a", "b", "c"];
console.log(userData.userName);
console.log(listData.join(", "));
5実務: 関数の戻り値の型を取り出して再利用する
戻り値の型に名前を付けていない関数から、infer で戻り値の型を取り出して他の関数で再利用する実務例です。createSession の戻り値の形を変えても Session 型は自動で追従します。これがないと戻り値と同じ形の型を手書きで定義して二重管理することになります。後のレッスンで学ぶ組み込みの ReturnType は、まさにこの実装です。
function createSession(userName: string) {
return { userName, token: "tok-001", expiresIn: 3600 };
}
// 関数型の戻り値の型を R として取り出す(組み込みの ReturnType と同じ仕組み)
type MyReturn<F> = F extends (...args: any[]) => infer R ? R : never;
// createSession の戻り値の形を変えれば、Session も自動で追従する
type Session = MyReturn<typeof createSession>;
function describeSession(session: Session): string {
return session.userName + " のセッション(残り " + session.expiresIn + " 秒)";
}
console.log(describeSession(createSession("Taro")));