TypeScript 5.4/5.5の新機能完全ガイド - 型推論とパフォーマンスの進化

2025.12.02

TypeScript 5.4および5.5では、型推論の改善、パフォーマンスの向上、そして新しいユーティリティ型が追加されました。本記事では、これらの新機能を実践的なコード例とともに解説します。

TypeScript 5.4の新機能

NoInfer ユーティリティ型

NoInfer<T>は、型パラメータの推論を抑制する新しいユーティリティ型です。

// NoInfer - 型推論の制御

// 問題: 意図しない型推論
function createState<T>(initial: T, allowed: T[]): T {
  return allowed.includes(initial) ? initial : allowed[0];
}

// "red" | "blue" ではなく string と推論される
const state1 = createState("red", ["red", "blue", "green"]);

// NoInferで解決
function createStateFixed<T>(initial: T, allowed: NoInfer<T>[]): T {
  return allowed.includes(initial) ? initial : allowed[0];
}

// "red" | "blue" | "green" と正しく推論
const state2 = createStateFixed("red", ["red", "blue", "green"]);

// 実用例: イベントハンドラー
type EventMap = {
  click: { x: number; y: number };
  keypress: { key: string };
  scroll: { scrollY: number };
};

function addEventListener<K extends keyof EventMap>(
  event: K,
  handler: (data: NoInfer<EventMap[K]>) => void
): void {
  // 実装
}

// handlerの型がEventMap[K]から推論されない
addEventListener("click", (data) => {
  // data は { x: number; y: number } として正しく型付け
  console.log(data.x, data.y);
});

クロージャ内のナローイング改善

関数やコールバック内での型のナローイングが大幅に改善されました。

// 改善されたクロージャ内のナローイング

function processData(value: string | number | null) {
  // TypeScript 5.3以前: コールバック内でナローイングが失われる
  // TypeScript 5.4: ナローイングが維持される

  if (value === null) {
    return;
  }

  // ここでvalueは string | number

  const handlers = {
    // TS 5.4: クロージャ内でもナローイングが維持
    process: () => {
      // valueは string | number として認識
      if (typeof value === "string") {
        return value.toUpperCase();
      }
      return value.toFixed(2);
    },
    log: () => {
      // ここでもナローイング維持
      console.log(value);
    }
  };

  return handlers.process();
}

// 条件分岐後のナローイング
function example(arr: string[] | null) {
  if (arr === null) {
    return;
  }

  // TypeScript 5.4: mapコールバック内でもarrはstring[]
  arr.map((item) => {
    // arrはnullではないことが保証
    console.log(arr.length); // OK
    return item.toUpperCase();
  });
}

// より複雑な例
type Result<T> = { success: true; data: T } | { success: false; error: string };

function processResult<T>(result: Result<T>) {
  if (!result.success) {
    return null;
  }

  // result.dataが利用可能
  const transformers = {
    // クロージャ内でもresult.successがtrueであることが認識される
    transform: () => {
      return result.data; // TS 5.4: OK
    }
  };

  return transformers.transform();
}

Object.groupByの型サポート

ES2024のObject.groupByMap.groupByに型サポートが追加されました。

// Object.groupBy - ES2024対応

interface User {
  id: string;
  name: string;
  role: 'admin' | 'user' | 'guest';
  department: string;
}

const users: User[] = [
  { id: '1', name: 'Alice', role: 'admin', department: 'Engineering' },
  { id: '2', name: 'Bob', role: 'user', department: 'Engineering' },
  { id: '3', name: 'Charlie', role: 'user', department: 'Sales' },
  { id: '4', name: 'Diana', role: 'guest', department: 'Marketing' },
];

// roleでグループ化
const byRole = Object.groupBy(users, (user) => user.role);
// 型: Partial<Record<'admin' | 'user' | 'guest', User[]>>

console.log(byRole.admin); // [{ id: '1', name: 'Alice', ... }]
console.log(byRole.user);  // [{ id: '2', ... }, { id: '3', ... }]

// departmentでグループ化
const byDepartment = Object.groupBy(users, (user) => user.department);
// 型: Partial<Record<string, User[]>>

// Map.groupBy - Mapを返す
const byRoleMap = Map.groupBy(users, (user) => user.role);
// 型: Map<'admin' | 'user' | 'guest', User[]>

byRoleMap.forEach((users, role) => {
  console.log(`${role}: ${users.length} users`);
});

// 実用例: 日付でグループ化
interface Order {
  id: string;
  amount: number;
  date: Date;
}

function groupOrdersByMonth(orders: Order[]) {
  return Object.groupBy(orders, (order) => {
    const month = order.date.toISOString().slice(0, 7); // "2025-12"
    return month;
  });
}

TypeScript 5.5の新機能

推論された型述語

関数の戻り値から型ガードが自動的に推論されるようになりました。

// 推論された型述語 (Type Predicates)

// TS 5.4以前: 明示的な型述語が必要
function isStringOld(value: unknown): value is string {
  return typeof value === 'string';
}

// TS 5.5: 自動的に型述語が推論される
function isString(value: unknown) {
  return typeof value === 'string';
}
// 推論される型: (value: unknown) => value is string

// 配列フィルターでの活用
const mixed: (string | number | null)[] = ['a', 1, null, 'b', 2];

// TS 5.4以前: filterの結果は (string | number | null)[] のまま
const stringsOld = mixed.filter((x): x is string => typeof x === 'string');

// TS 5.5: 自動的にstring[]と推論
const strings = mixed.filter((x) => typeof x === 'string');
// 型: string[]

// nullの除去も自動
const notNull = mixed.filter((x) => x !== null);
// 型: (string | number)[]

// 複雑な例
interface User {
  id: string;
  name: string;
  email?: string;
}

const users: (User | null)[] = [
  { id: '1', name: 'Alice', email: 'alice@example.com' },
  null,
  { id: '2', name: 'Bob' },
];

// 自動的に型が絞り込まれる
const validUsers = users.filter((user) => user !== null && user.email);
// 型: User[] (emailが存在するユーザーのみ)

正規表現の構文チェック

正規表現リテラルの構文チェックが追加されました。

// 正規表現の構文チェック

// TS 5.5: 無効な正規表現はコンパイルエラー

// エラー: 無効なエスケープシーケンス
// const invalid1 = /\p/;

// エラー: 不完全な文字クラス
// const invalid2 = /[a-/;

// エラー: 無効な量指定子
// const invalid3 = /a{}/;

// 有効な正規表現
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const phone = /^\d{3}-\d{4}-\d{4}$/;
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

// Unicodeプロパティエスケープ(uフラグが必要)
const japanese = /\p{Script=Hiragana}+/u;
const emoji = /\p{Emoji}/u;

// 名前付きキャプチャグループ
const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-02'.match(datePattern);
if (match?.groups) {
  const { year, month, day } = match.groups;
  console.log(year, month, day); // "2025", "01", "02"
}

独立した宣言 (Isolated Declarations)

isolatedDeclarationsオプションにより、型宣言の生成を高速化できます。

// tsconfig.json
{
  "compilerOptions": {
    "isolatedDeclarations": true,
    "declaration": true
  }
}

// isolatedDeclarations: true の場合、
// 関数の戻り値や変数の型を明示する必要がある

// ❌ エラー: 戻り値の型が必要
// export function add(a: number, b: number) {
//   return a + b;
// }

// ✅ OK: 戻り値の型を明示
export function add(a: number, b: number): number {
  return a + b;
}

// ❌ エラー: 変数の型が必要
// export const config = { port: 3000, host: 'localhost' };

// ✅ OK: 型を明示
export const config: { port: number; host: string } = {
  port: 3000,
  host: 'localhost'
};

// 利点:
// - ビルドの並列化が可能に
// - 型チェックなしで.d.tsを生成可能
// - モノレポでの高速ビルド

新しいSet メソッドの型サポート

ES2024のSetメソッドに型サポートが追加されました。

// 新しいSetメソッド

const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);

// union: 和集合
const union = setA.union(setB);
console.log([...union]); // [1, 2, 3, 4, 5, 6]

// intersection: 積集合
const intersection = setA.intersection(setB);
console.log([...intersection]); // [3, 4]

// difference: 差集合
const difference = setA.difference(setB);
console.log([...difference]); // [1, 2]

// symmetricDifference: 対称差
const symmetricDiff = setA.symmetricDifference(setB);
console.log([...symmetricDiff]); // [1, 2, 5, 6]

// isSubsetOf: 部分集合判定
const subset = new Set([2, 3]);
console.log(subset.isSubsetOf(setA)); // true

// isSupersetOf: 上位集合判定
console.log(setA.isSupersetOf(subset)); // true

// isDisjointFrom: 互いに素か判定
const setC = new Set([7, 8, 9]);
console.log(setA.isDisjointFrom(setC)); // true

// 実用例: タグフィルタリング
interface Article {
  id: string;
  title: string;
  tags: Set<string>;
}

function filterByTags(articles: Article[], requiredTags: Set<string>): Article[] {
  return articles.filter(article =>
    requiredTags.isSubsetOf(article.tags)
  );
}

パフォーマンス改善

型チェックの高速化

// TypeScript 5.4/5.5 パフォーマンス改善

// 1. モノモーフィック化された型チェック
// - 同じ形状のオブジェクトの処理が高速化

// 2. 条件型の最適化
type IsString<T> = T extends string ? true : false;
// 内部的なキャッシングが改善

// 3. プロジェクト参照のビルド最適化
// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "composite": true,
    // 5.5で追加
    "isolatedDeclarations": true
  },
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" }
  ]
}

// 4. エディタの応答性向上
// - 自動インポートの提案が高速化
// - リファクタリング操作の改善

実践的な型パターン

改善された型ユーティリティ

// 実践的な型パターン

// 1. より安全なディープパーシャル
type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

interface Config {
  server: {
    port: number;
    host: string;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
  database: {
    url: string;
    pool: number;
  };
}

// 部分的な設定更新
function updateConfig(partial: DeepPartial<Config>): Config {
  // マージロジック
  return {} as Config;
}

updateConfig({
  server: {
    ssl: {
      enabled: true
    }
  }
});

// 2. 型安全なイベントシステム
type EventMap = {
  'user:login': { userId: string; timestamp: Date };
  'user:logout': { userId: string };
  'order:created': { orderId: string; total: number };
};

class TypedEventEmitter<T extends Record<string, unknown>> {
  private handlers = new Map<keyof T, Set<(data: any) => void>>();

  on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    this.handlers.get(event)!.add(handler);

    return () => this.handlers.get(event)?.delete(handler);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.handlers.get(event)?.forEach(handler => handler(data));
  }
}

const emitter = new TypedEventEmitter<EventMap>();

// 型安全なイベントハンドリング
emitter.on('user:login', (data) => {
  // data は { userId: string; timestamp: Date }
  console.log(data.userId, data.timestamp);
});

// 3. 条件付き必須プロパティ
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
  Pick<T, Exclude<keyof T, Keys>> &
  { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys];

interface SearchParams {
  query?: string;
  categoryId?: string;
  tags?: string[];
}

// 少なくとも1つの検索条件が必須
type ValidSearchParams = RequireAtLeastOne<SearchParams, 'query' | 'categoryId' | 'tags'>;

function search(params: ValidSearchParams) {
  // 実装
}

// OK
search({ query: 'test' });
search({ categoryId: '123' });
search({ query: 'test', tags: ['a', 'b'] });

// エラー
// search({}); // 少なくとも1つの条件が必要

マイグレーションガイド

5.3 → 5.4/5.5 への移行

# アップグレード
npm install typescript@latest

# 型チェック
npx tsc --noEmit

# よくある修正点
// 1. 新しいナローイング挙動への対応
// 一部のコードで型が正しく推論されるようになり、
// 以前は必要だった型アサーションが不要になる場合がある

// Before (TS 5.3)
function example(value: string | null) {
  if (value === null) return;

  const fn = () => {
    // value as string が必要だった
    return (value as string).toUpperCase();
  };
}

// After (TS 5.4+)
function exampleNew(value: string | null) {
  if (value === null) return;

  const fn = () => {
    // 型アサーション不要
    return value.toUpperCase();
  };
}

// 2. Object.groupBy の使用
// lib設定の確認が必要
// tsconfig.json
{
  "compilerOptions": {
    "lib": ["ES2024"] // または "ESNext"
  }
}

まとめ

TypeScript 5.4/5.5は、型推論とパフォーマンスの大幅な改善をもたらしました。

主要な新機能

機能バージョン影響
NoInfer5.4型推論の制御
クロージャナローイング5.4コード品質向上
Object.groupBy5.4ES2024対応
推論された型述語5.5filter等の改善
isolatedDeclarations5.5ビルド高速化
正規表現チェック5.5エラー検出強化

アップグレードの推奨

  • 新規プロジェクト: 最新版を採用
  • 既存プロジェクト: 段階的に移行
  • 大規模プロジェクト: isolatedDeclarationsの活用

TypeScriptの進化により、より安全で効率的なコードが書けるようになっています。

参考リンク

← 一覧に戻る