LoginSignup
7
1

inferが最近使えるようになってきた話

Posted at

こんにちは!
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

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1