条件型の分配則、infer によるパターンマッチング、そして never・any・boolean といった特殊ケースを使いこなす。
このレッスンを Kodokon で開く条件型は型システムにおける分岐の仕組みです。T extends U ? X : Y は、コンパイラに T が U に代入可能かどうかを問い、その結果に応じて分岐を選びます。ReturnType、Exclude、Awaited などの標準ユーティリティはすべてこの上に構築されています。このレベルで問われるのは、もはや書き方ではなく、コンパイラがそれをどう評価するかを正確に予測することであり、特にユニオンや never が絡む場合に重要になります。
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">;
// true
type B = IsString<string | number>;
// boolean, not false!鍵となる挙動は分配則です。テスト対象の型が裸の型パラメータ(タプル・配列・交差型で包まれていないもの)である場合、条件はユニオンの各メンバーに対して個別に適用され、その結果が結合されます。したがって IsString<string | number> は true | false、つまり boolean を計算します。この分配を無効化するには、両辺をタプルで包みます: [T] extends [U]。恐るべき特殊ケースが一つあります: never は空のユニオンです。0 個のメンバーに対する分配はどの分岐も評価しないため、どちらの分岐を書いたかにかかわらず結果は never になります。
type ToArray<T> = T extends unknown ? T[] : never;
type NoDist<T> = [T] extends [unknown] ? T[] : never;
type A = ToArray<string | number>;
// string[] | number[]
type B = NoDist<string | number>;
// (string | number)[]
type IsNever<T> = [T] extends [never] ? true : false;
type C = IsNever<never>; // true thanks to the tupleinfer キーワードは、パターンマッチングを通じて捕捉される型変数を導入します。コンパイラはテスト対象の型の構造をパターンと照合しようとし、マッチが成功すればその変数を束縛します。同じ名前で複数の infer 宣言を置くこともできます。共変の位置(戻り値の型など、生成される値)では、候補はユニオンにまとめられ、反変の位置(関数の引数)では交差型にまとめられます。この規則は変性から直接導かれます。それぞれの方向で唯一安全な選択だからです。
type ElementOf<T> =
T extends readonly (infer E)[] ? E : never;
type FirstParam<T> =
T extends (arg: infer P) => unknown ? P : never;
type Merged<T> =
T extends { a: (x: infer P) => void;
b: (x: infer P) => void }
? P : never;
type M = Merged<{
a: (x: { id: string }) => void;
b: (x: { tag: string }) => void;
}>; // { id: string } & { tag: string }TypeScript 4.7 以降、infer はインラインの制約を受け付けます: infer N extends number は捕捉をフィルタリングし、4.8 以降ではコンパイラが数値の文字列リテラルを数値リテラルへ変換までしてくれます。もう一つの仕様上の細かな点: 入力にまだ解決されていないジェネリックパラメータを含む条件型は遅延されます。それはインスタンス化の箇所でのみ評価されるため、一部のエラーが呼び出し側でしか現れない理由がここにあります。
type ToNumber<S extends string> =
S extends `${infer N extends number}` ? N : never;
type Port = ToNumber<"8080">; // the literal 8080
type Bad = ToNumber<"abc">; // neverExclude<boolean, true> は何になりますか?type IsNever<T> = T extends never ? true : false; において、IsNever<never> は何になりますか?infer P が生成するのは: