TypeScript 5.4の新機能 - NoInferとクロージャの型絞り込み

6分 で読める | 2025.12.02

公式ドキュメント

TypeScript 5.4は2024年3月6日にreleaseされました。この記事は5.4固有の型推論とlibrary定義に限定し、5.5の機能は**TypeScript 5.5の記事**へ分離します。

NoInfer

記事情報: 2025年12月2日初出。TypeScript 5.4公式release記事を2026年7月25日に再確認しています。

generic functionでは複数のargumentが同じtype parameterの推論候補になります。候補にしたくない位置をNoInfer<T>で囲むと、その値からTを推論せず、ほかのargumentから決まった型へ適合するかだけを確認できます。

function createTheme<C extends string>(
  colors: C[],
  initial?: NoInfer<C>,
) {
  return { colors, initial };
}

createTheme(["red", "green"], "red");
createTheme(["red", "green"], "blue");
//                            ~~~~~~
// "red" | "green" に含まれないためerror

従来は2つ目のtype parameterを作るworkaroundが使われましたが、constraintだけのparameterが増え、意図しない推論を許す場合がありました。

NoInferは値のruntime validationを行いません。外部JSONなど実行時の入力にはschema validationが別に必要です。また、推論結果を理解しづらくするほど多用せず、「このargumentは候補集合を広げてはいけない」というAPI境界に使います。

クロージャ内のnarrowing

TypeScriptは変数へ最後に代入した位置を分析し、その後変更されないと判断できる場合、closure内でも型の絞り込みを保持します。

function getUrls(input: string | URL, names: string[]) {
  if (typeof input === "string") {
    input = new URL(input);
  }

  return names.map((name) => {
    input.searchParams.set("name", name);
    return input.toString();
  });
}

5.4ではmap callback作成前の最後の代入を見て、callback内のinputURLとして扱えます。

ただしnested function内で対象変数を再代入する場合、いつ実行されるか保証できないためnarrowingは維持されません。

function example(value: string | undefined) {
  if (value === undefined) return;

  setTimeout(() => {
    value = undefined;
  });

  return () => value.toUpperCase();
  //           ~~~~~
  // 再代入の可能性がある
}

型errorを消すためだけにassertionを加えず、変数をconstへ写す、mutationを分離するなどcontrol flowを明確にします。

Object.groupByとMap.groupByの型

TypeScript 5.4はECMAScriptのObject.groupByMap.groupByの型定義を追加しました。

const values = [1, 2, 3, 4];

const grouped = Object.groupBy(values, (value) =>
  value % 2 === 0 ? "even" : "odd",
);

console.log(grouped.even);

一般にはcallbackがすべてのkeyを必ず生成する保証がないため、resultのpropertyはoptionalです。strictNullChecksでは存在確認が必要です。

const evenTotal =
  grouped.even?.reduce((sum, value) => sum + value, 0) ?? 0;

これらはTypeScript独自のruntime実装ではなくJavaScript APIの型です。compileが通っても実行環境にAPIがなければ動きません。release当時はtarget: "esnext"または適切なlib設定が必要と案内されていました。現在はtarget browser・Node.jsとpolyfill方針を確認します。

Map.groupByはobject property key以外の値もgroup keyに使いたい場合に選択できます。

Promise.resolveの型改善

5.4ではPromise.resolveの型がAwaitedを使う形へ改善され、Promiseを受け取ったgeneric codeで返却型をより正確に表せるようになりました。独自utilityでPromiseをunwrapしていた場合は推論結果が変わる可能性があります。

型推論の改善はruntime behaviorを変えません。compile errorが新しく出た場合、以前の型が過度に広かったのか、public APIのannotationが不足しているのかを確認します。

Auto-importとmodule resolution

5.4はpackageのexportsにあるsubpath importをauto-import候補でより適切に扱い、moduleResolution: "bundler""preserve"の組み合わせなどmodule関連も改善しました。

package開発ではeditorの候補だけで判断せず、生成されるimport pathがpackage exportsに含まれ、runtime bundlerでもresolveできるかをtestします。

Deprecated optionの扱い

5.0でdeprecatedになったcompiler optionは5.4で機能しなくなったものがあります。代表例にはcharsetimportsNotUsedAsValuespreserveValueImportsなどが含まれます。ignoreDeprecations: "5.0"でwarningだけを隠していたprojectは設定を削除・置換します。

npx tsc --showConfig

extended tsconfigを含む最終設定を確認し、古いoptionを残さないようにします。

移行手順

npm install --save-dev typescript@5.4
npx tsc --noEmit
  1. 現在のTypeScriptでerrorを解消する
  2. 5.4へ固定して型checkする
  3. public libraryはdeclaration fileの差分を見る
  4. testとbuildを実行する
  5. editorがworkspace版TypeScriptを使うか確認する

TypeScriptのminor updateではruntime codeが同じでも推論結果が変わります。anyやassertionで一括回避せず、errorごとに意図した型を確認してください。

5.4は現在のlatest版ではありません。新規projectではframeworkが対応する現在のstable版を使い、この記事は5.4で型の挙動が変わった理由を追う資料として利用してください。複数versionを飛び越えて更新する場合も、各release noteを順番に確認するとerrorの発生源を特定しやすくなります。

参考リソース

← 一覧に戻る
PR
PR
PR
PR