Featured image of post 型チェックを厳格化するTypeScriptのNoInferユーティリティ型 Featured image of post 型チェックを厳格化するTypeScriptのNoInferユーティリティ型

型チェックを厳格化するTypeScriptのNoInferユーティリティ型

TypeScript 5.4以降で導入されたNoInferユーティリティ型の使い方を徹底解説。ジェネリクス関数において特定の型引数からの推論を無効化し、意図しない型拡大(Widening)を防ぐことで型チェックを厳格化する実践的なテクニックを具体的なコード例とともに紹介します。

型の拡大(Widening)問題

TypeScript が複数の呼び出しサイト引数から型を推論するとき、すべての候補を包含するように型を拡大(widen) することがあります。通常は便利ですが、特定のジェネリクスシナリオでは緩すぎる型が生成され、不正な状態がすり抜けてしまいます。

キーとデフォルト値を受け取る関数を考えます:

function createState<T extends string>(key: T, defaultValue: T) {
  return { key, defaultValue };
}

const state = createState("color", "blue");
// T は "color" | "blue" と推論される — 望ましくない

TypeScript は T"color" | "blue" と推論します。本来は defaultValuekey に一致することを制約したいはずです。

NoInfer の登場

TypeScript 5.4 で導入された NoInfer<T> は、コンパイラに対して この位置を型推論に使わないでください と指示します。型自体は T に対してチェックされますが、T の解決に候補を提供しません。

function createState<T extends string>(
  key: T,
  defaultValue: NoInfer<T>
) {
  return { key, defaultValue };
}

const state = createState("color", "blue");
// T は "color" と推論される
// "blue" は "color" に対してチェックされる — OK

不一致の値を渡すと明確なエラーになります:

createState("color", 42);
// Error: Type 'number' is not assignable to type '"color"'

NoInfer の仕組み

NoInfer<T>lib.es5.d.ts で次のように定義されています:

type NoInfer<T> = [T][T extends any ? 0 : never];

条件型を使って推論をブロックしています。コンパイラはこの位置を非推論可能と見なします。実際の型はチェック用に T に解決されます。

ユースケース1 — 関数パラメータ

型付きペイロードを持つイベントエミッタ:

function onEvent<E extends string, P>(
  event: E,
  handler: (payload: NoInfer<P>) => void
): void;

onEvent("click", (p: MouseEvent) => {}); // P = MouseEvent
onEvent("click", (p: number) => {});
// Error: 'number' is not assignable to 'MouseEvent'

ユースケース2 — タプル推論

function pair<T extends readonly any[]>(
  first: [...T],
  second: NoInfer<[...T]>
): T;

const p = pair([1, "a"], [2, "b"]);
// T は [number, string] と推論 — 正しい

ユースケース3 — ジェネリクス制約

制約型からの推論を防止:

function lookup<T, K extends keyof T>(
  obj: T,
  key: K,
  fallback: NoInfer<T[K]>
): T[K];

const val = lookup({ a: 1, b: "hello" }, "a", 0);
// val: number — 正しい

lookup({ a: 1 }, "a", "wrong");
// Error: 'string' is not assignable to 'number'

代替手段との比較

アプローチ動作
NoInfer なしすべての候補のユニオンに拡大
明示的な型パラメータ呼び出し元が手動で型を指定
NoInfer選択した位置からの推論を防止
別の型パラメータ不必要な複雑さを追加

NoInfer を使うべき場面

  • 2つ以上のパラメータが型パラメータを共有 し、一方が推論元、もう一方がチェック対象である場合
  • 戻り値型の推論を保護 し、呼び出し元引数による拡大を防ぐ場合
  • ビルダーパターン — チェーンメソッド間での型汚染を防ぐ場合
class Builder<T extends Record<string, unknown>> {
  set<K extends string, V>(
    key: K,
    value: NoInfer<V>
  ): Builder<T & Record<K, V>> {
    return this;
  }
}

まとめ

NoInfer<T> はジェネリクスの推論を厳格化するための焦点特化型ツールです。特定のパラメータ位置を推論対象外とマークすることで不要な拡大を防ぎ、型互換性は維持します。ひとつの引数が型のアンカーとなり、他の引数がそれに従うべき場面で活用してください。結果として、より正確な型、優れた IDE 補完、そして明確なコンパイルエラーが得られます。