T-CREATOR

TypeScript satisfies 演算子の実力:型の過剰/不足を一発検知する実践ガイド

TypeScript satisfies 演算子の実力:型の過剰/不足を一発検知する実践ガイド

TypeScript 4.9 で登場した satisfies 演算子は、型の安全性を保ちながらも型推論の柔軟性を失わない画期的な機能です。従来の as や型注釈では実現できなかった「型の過剰チェック」と「型の不足チェック」を同時に実現し、より堅牢なコードを書けるようになりました。

この記事では、satisfies 演算子の実力を余すところなく解説し、実務で即活用できる実践的なテクニックをご紹介します。

背景

TypeScript における型チェックの課題

TypeScript では、変数の型を指定する方法として「型注釈」と「型アサーション(as)」が存在します。しかし、それぞれに異なる問題点がありました。

型注釈を使用すると、型の安全性は保証されますが、型推論が失われてしまいます。一方、型アサーションは型推論を保持できますが、安全性が犠牲になるのです。

以下の図で、従来の型指定方法の違いを確認しましょう。

mermaidflowchart TD
  start["変数への型指定"] --> choice{"どちらを選ぶ?"}
  choice -->|型注釈| anno["型注釈: const x: Type = value"]
  choice -->|型推論| infer["型推論: const x = value"]

  anno --> anno_pro["✓ 型の安全性確保"]
  anno --> anno_con["✗ 詳細な型情報が失われる"]

  infer --> infer_pro["✓ 詳細な型情報を保持"]
  infer --> infer_con["✗ 型の制約チェックなし"]

  anno_pro --> problem["ジレンマ:<br/>安全性 vs 柔軟性"]
  anno_con --> problem
  infer_pro --> problem
  infer_con --> problem

この図から、従来の方法では「型の安全性」と「型推論の柔軟性」を両立できないジレンマがあることがわかります。

型注釈の問題点

型注釈を使うと、TypeScript は値が指定した型に適合しているかをチェックしますが、推論される型は注釈で指定した型になります。

typescript// 型注釈を使用した例
type Color = 'red' | 'green' | 'blue';

const color: Color = 'red';

上記のコードでは、color の型は Color 型として扱われます。しかし、実際の値は "red" というリテラル型なのに、その詳細な情報が失われてしまうのです。

typescript// color の型は Color 型("red" | "green" | "blue")
// 実際の値 "red" というリテラル型の情報は失われる
console.log(color); // "red"

このように、型注釈では値の詳細な型情報を保持できません。

型アサーションの問題点

型アサーション(as)を使用すると、型推論は保持されますが、TypeScript が本来行うべき型チェックが無効化されてしまいます。

typescript// 型アサーションの例
const config = {
  endpoint: 'https://api.example.com',
  timeout: 3000,
} as const;

型アサーションは「私はこの型が正しいと確信している」と TypeScript に伝える機能ですが、間違った型を指定してもエラーにならない危険性があります。

typescript// 危険な例:間違った型でもエラーにならない
type ApiConfig = {
  endpoint: string;
  timeout: number;
  retries: number; // 必須プロパティ
};

// retries が欠けているのにエラーにならない!
const config = {
  endpoint: 'https://api.example.com',
  timeout: 3000,
} as ApiConfig;

このように、型アサーションでは型の不足を検知できないのです。

課題

従来の型指定方法の限界

TypeScript で型安全なコードを書く際、開発者は以下の 3 つの課題に直面していました。

#課題影響
1型注釈では詳細な型情報が失われるプロパティアクセス時に型絞り込みができない
2型アサーションでは型の不足を検知できない実行時エラーのリスクが高まる
3型の過剰(余分なプロパティ)を検知できない意図しないプロパティが混入する

これらの課題を具体的なコードで見ていきましょう。

課題 1:型注釈による型情報の損失

設定オブジェクトを扱う実例で考えてみます。

typescript// 設定の型定義
type Config = {
  apiEndpoint: string;
  timeout: number;
  features: {
    enableCache: boolean;
    enableMetrics: boolean;
  };
};

型注釈を使って設定オブジェクトを定義すると、リテラル型の情報が失われます。

typescript// 型注釈を使用
const config: Config = {
  apiEndpoint: 'https://api.example.com',
  timeout: 5000,
  features: {
    enableCache: true,
    enableMetrics: false,
  },
};

// config.apiEndpoint の型は string(リテラル型ではない)
// そのため、厳密な文字列比較ができない

この問題により、条件分岐などで厳密な型チェックを行いたい場合に不便が生じます。

課題 2:型の不足を検知できない

必須プロパティが欠けていても、型アサーションではエラーになりません。

typescripttype UserProfile = {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
};

// email が欠けているのにエラーにならない
const profile = {
  id: '123',
  name: '山田太郎',
  role: 'user',
} as UserProfile;

// 実行時エラーになる可能性がある
console.log(profile.email.toLowerCase());

このようなコードは、コンパイル時にはエラーにならず、実行時に初めて問題が発覚します。

課題 3:型の過剰を検知できない

意図しないプロパティが含まれていても、型アサーションでは検知されません。

typescripttype ButtonProps = {
  label: string;
  onClick: () => void;
};

// typo: onCllick(誤字)が含まれているが検知されない
const props = {
  label: '送信',
  onClick: () => console.log('clicked'),
  onCllick: () => console.log('typo!'), // 誤字だがエラーにならない
} as ButtonProps;

これらの課題を図で整理すると、以下のようになります。

mermaidflowchart TD
  problems["従来の型指定の課題"] --> p1["課題1: 型情報の損失"]
  problems --> p2["課題2: 型の不足を検知できない"]
  problems --> p3["課題3: 型の過剰を検知できない"]

  p1 --> p1_detail["型注釈で<br/>リテラル型が失われる"]
  p2 --> p2_detail["型アサーションで<br/>必須プロパティ欠落を見逃す"]
  p3 --> p3_detail["型アサーションで<br/>余分なプロパティを見逃す"]

  p1_detail --> risk1["リスク: 型絞り込み不可"]
  p2_detail --> risk2["リスク: 実行時エラー"]
  p3_detail --> risk3["リスク: バグの温床"]

これらの課題を解決するために、satisfies 演算子が導入されました。

解決策

satisfies 演算子の登場

TypeScript 4.9 で導入された satisfies 演算子は、上記の課題をすべて解決する画期的な機能です。

satisfies 演算子の基本構文は以下の通りです。

typescript// 基本構文
const value = expression satisfies Type;

satisfies は以下の 2 つを同時に実現します。

  1. 型の制約チェック:値が指定した型を満たしているか検証する
  2. 型推論の保持:値の詳細な型情報(リテラル型など)を保持する

この仕組みを図で表すと以下のようになります。

mermaidflowchart LR
  value["値<br/>(expression)"] --> satisfies["satisfies 演算子"]
  type_def["型定義<br/>(Type)"] --> satisfies

  satisfies --> check1["✓ 型の制約チェック"]
  satisfies --> check2["✓ 型推論の保持"]

  check1 --> result1["過剰/不足を検知"]
  check2 --> result2["リテラル型を保持"]

  result1 --> benefit["安全性と柔軟性の<br/>両立"]
  result2 --> benefit

satisfies の基本的な使い方

まずは、最もシンプルな使用例から見ていきます。

typescript// カラー定義
type Color = 'red' | 'green' | 'blue';

// satisfies を使用
const primaryColor = 'red' satisfies Color;

このコードでは、"red"Color 型を満たしていることを検証しつつ、primaryColor の型はリテラル型 "red" として推論されます。

typescript// primaryColor の型は "red"(リテラル型)
// Color 型ではなく、より具体的な型が保持される

// 型エラーになる例
const invalidColor = 'yellow' satisfies Color;
// Error: Type '"yellow"' does not satisfy the expected type 'Color'.

この例から、satisfies が型チェックを行いながらも、詳細な型情報を失わないことがわかります。

課題 1 の解決:型情報の保持

型注釈で失われていた詳細な型情報を、satisfies で保持できます。

typescripttype Config = {
  apiEndpoint: string;
  timeout: number;
  features: {
    enableCache: boolean;
    enableMetrics: boolean;
  };
};

satisfies を使用すると、型チェックを行いつつ、リテラル型を保持できます。

typescript// satisfies を使用
const config = {
  apiEndpoint: 'https://api.example.com',
  timeout: 5000,
  features: {
    enableCache: true,
    enableMetrics: false,
  },
} satisfies Config;

// config.apiEndpoint の型は "https://api.example.com"(リテラル型)
// config.timeout の型は 5000(リテラル型)
// config.features.enableCache の型は true(リテラル型)

これにより、条件分岐で厳密な型チェックが可能になります。

typescript// 厳密な型チェックが可能
if (config.apiEndpoint === 'https://api.example.com') {
  // TypeScript が厳密に型を理解している
  console.log('Production API を使用');
}

課題 2 の解決:型の不足を検知

satisfies は、必須プロパティが欠けている場合にエラーを発生させます。

typescripttype UserProfile = {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
};

必須プロパティが欠けていると、コンパイル時にエラーになります。

typescript// email が欠けているのでエラーになる
const profile = {
  id: '123',
  name: '山田太郎',
  role: 'user',
} satisfies UserProfile;
// Error: Property 'email' is missing in type
// '{ id: string; name: string; role: "user"; }'
// but required in type 'UserProfile'.

これにより、実行時エラーを未然に防げます。

課題 3 の解決:型の過剰を検知

satisfies は、型定義にないプロパティが含まれている場合もエラーを発生させます。

typescripttype ButtonProps = {
  label: string;
  onClick: () => void;
};

余分なプロパティがあると、コンパイル時にエラーになります。

typescript// typo: onCllick(誤字)があるのでエラーになる
const props = {
  label: '送信',
  onClick: () => console.log('clicked'),
  onCllick: () => console.log('typo!'),
} satisfies ButtonProps;
// Error: Object literal may only specify known properties,
// and 'onCllick' does not exist in type 'ButtonProps'.

これにより、タイポや意図しないプロパティの混入を防げます。

具体例

実例 1:カラーパレット設定

デザインシステムのカラーパレットを定義する実例を見てみましょう。

typescript// カラー値の型定義
type ColorValue =
  | string
  | { r: number; g: number; b: number };

// パレット全体の型定義
type ColorPalette = {
  primary: ColorValue;
  secondary: ColorValue;
  accent: ColorValue;
};

satisfies を使って、型安全なカラーパレットを定義します。

typescript// satisfies を使用したカラーパレット定義
const palette = {
  primary: '#3b82f6',
  secondary: { r: 139, g: 92, b: 246 },
  accent: '#10b981',
} satisfies ColorPalette;

この定義により、以下のメリットが得られます。

typescript// primary の型は "#3b82f6"(リテラル型)
// 厳密な型チェックが可能
if (palette.primary === '#3b82f6') {
  console.log('デフォルトの青色を使用');
}

// secondary の型は { r: number; g: number; b: number }
// RGB 値として安全にアクセス可能
const red = palette.secondary.r; // 型安全

型の不足や過剰も検知されます。

typescript// エラー例 1:必須プロパティの欠落
const incompletePalette = {
  primary: '#3b82f6',
  secondary: '#8b5cf6',
  // accent が欠けている
} satisfies ColorPalette;
// Error: Property 'accent' is missing

// エラー例 2:余分なプロパティ
const excessivePalette = {
  primary: '#3b82f6',
  secondary: '#8b5cf6',
  accent: '#10b981',
  tertiary: '#f59e0b', // 定義にない
} satisfies ColorPalette;
// Error: 'tertiary' does not exist in type 'ColorPalette'

以下の図で、satisfies によるカラーパレット定義のフローを確認できます。

mermaidflowchart TD
  define["パレット定義<br/>(オブジェクトリテラル)"] --> satisfies["satisfies ColorPalette"]

  satisfies --> check1["✓ 必須プロパティ確認<br/>(primary, secondary, accent)"]
  satisfies --> check2["✓ 型の適合性確認<br/>(ColorValue に適合)"]
  satisfies --> check3["✓ 余分なプロパティ確認<br/>(未定義のプロパティ検知)"]

  check1 --> infer["型推論の保持"]
  check2 --> infer
  check3 --> infer

  infer --> result1["primary: '#3b82f6'<br/>(リテラル型)"]
  infer --> result2["secondary: &#123;r,g,b&#125;<br/>(オブジェクト型)"]
  infer --> result3["accent: '#10b981'<br/>(リテラル型)"]

実例 2:API レスポンスのバリデーション

API から取得したデータを型安全に扱う例です。

typescript// API レスポンスの型定義
type ApiResponse = {
  status: 'success' | 'error';
  data: {
    userId: string;
    userName: string;
  };
  timestamp: number;
};

satisfies を使って、モックデータを型安全に定義します。

typescript// モックデータの定義
const mockResponse = {
  status: 'success',
  data: {
    userId: 'user_123',
    userName: '田中花子',
  },
  timestamp: 1704067200000,
} satisfies ApiResponse;

型推論により、各プロパティの詳細な型が保持されます。

typescript// mockResponse.status の型は "success"(リテラル型)
// 条件分岐で型が絞り込まれる
if (mockResponse.status === 'success') {
  // この分岐内では status が "success" であることが確定
  console.log('成功レスポンス');
}

// data プロパティも詳細な型を保持
const userId: 'user_123' = mockResponse.data.userId;

API レスポンスのバリアントも型安全に定義できます。

typescript// エラーレスポンスの定義
const errorResponse = {
  status: 'error',
  data: {
    userId: '',
    userName: '',
  },
  timestamp: Date.now(),
} satisfies ApiResponse;

// status の型は "error"(リテラル型)
// 型の絞り込みが正確に機能する

実例 3:ルーティング設定

Next.js などのフレームワークで使用するルーティング設定の例です。

typescript// ルート設定の型定義
type RouteConfig = {
  [key: string]: {
    path: string;
    component: string;
    auth?: boolean;
  };
};

satisfies を使って、型安全なルート設定を定義します。

typescript// ルート設定の定義
const routes = {
  home: {
    path: '/',
    component: 'HomePage',
  },
  dashboard: {
    path: '/dashboard',
    component: 'DashboardPage',
    auth: true,
  },
  profile: {
    path: '/profile',
    component: 'ProfilePage',
    auth: true,
  },
} satisfies RouteConfig;

この定義により、ルート名を型安全に参照できます。

typescript// routes.home の型は詳細に推論される
// {
//   path: "/",
//   component: "HomePage"
// }

// path プロパティはリテラル型として扱われる
const homePath: '/' = routes.home.path;

// 存在しないルートを参照しようとするとエラーになる
const aboutRoute = routes.about;
// Error: Property 'about' does not exist

ルート設定の不備も検知されます。

typescript// エラー例:必須プロパティの欠落
const invalidRoutes = {
  home: {
    path: '/', // component が欠けている
  },
} satisfies RouteConfig;
// Error: Property 'component' is missing

// エラー例:型の不一致
const wrongTypeRoutes = {
  home: {
    path: '/',
    component: 'HomePage',
    auth: 'yes', // boolean ではない
  },
} satisfies RouteConfig;
// Error: Type 'string' is not assignable to type 'boolean | undefined'

実例 4:環境変数の型チェック

環境変数を型安全に扱う実例です。

typescript// 環境変数の型定義
type EnvConfig = {
  NODE_ENV: 'development' | 'production' | 'test';
  API_URL: string;
  API_KEY: string;
  PORT: number;
  ENABLE_LOGGING: boolean;
};

satisfies を使って、環境変数の設定を検証します。

typescript// 環境変数の設定(開発環境)
const devEnv = {
  NODE_ENV: 'development',
  API_URL: 'http://localhost:3000',
  API_KEY: 'dev_api_key_12345',
  PORT: 3000,
  ENABLE_LOGGING: true,
} satisfies EnvConfig;

各プロパティの型が詳細に推論されます。

typescript// devEnv.NODE_ENV の型は "development"(リテラル型)
// 環境ごとの条件分岐が型安全に
if (devEnv.NODE_ENV === 'development') {
  console.log('開発モードで実行中');
}

// devEnv.PORT の型は 3000(リテラル型)
// 厳密な値の比較が可能

本番環境の設定も型安全に定義できます。

typescript// 本番環境の設定
const prodEnv = {
  NODE_ENV: 'production',
  API_URL: 'https://api.example.com',
  API_KEY: process.env.API_KEY || '',
  PORT: parseInt(process.env.PORT || '8080'),
  ENABLE_LOGGING: false,
} satisfies EnvConfig;

// prodEnv.NODE_ENV の型は "production"(リテラル型)
// 環境に応じた型の絞り込みが可能

環境変数設定のフローを図で表すと以下のようになります。

mermaidsequenceDiagram
  participant Dev as 開発者
  participant Code as コード
  participant TS as TypeScript
  participant Runtime as 実行時

  Dev->>Code: 環境変数を定義<br/>(satisfies EnvConfig)
  Code->>TS: 型チェック要求

  TS->>TS: 必須プロパティ確認
  TS->>TS: 型の適合性確認
  TS->>TS: リテラル型推論

  alt 型エラーあり
    TS-->>Dev: コンパイルエラー<br/>(不足/過剰を通知)
  else 型チェックOK
    TS->>Code: 型推論結果を保持
    Code->>Runtime: 型安全に実行
    Runtime-->>Dev: 実行時エラーなし
  end

実例 5:フォームバリデーション

React フォームのバリデーションルールを定義する例です。

typescript// バリデーションルールの型定義
type ValidationRule = {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  pattern?: RegExp;
  message?: string;
};

type FormValidation = {
  [fieldName: string]: ValidationRule;
};

satisfies を使って、型安全なバリデーションルールを定義します。

typescript// ユーザー登録フォームのバリデーション
const userFormValidation = {
  email: {
    required: true,
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    message: '有効なメールアドレスを入力してください',
  },
  password: {
    required: true,
    minLength: 8,
    maxLength: 32,
    message:
      'パスワードは8文字以上32文字以下で入力してください',
  },
  username: {
    required: true,
    minLength: 3,
    maxLength: 20,
    pattern: /^[a-zA-Z0-9_]+$/,
    message:
      'ユーザー名は英数字とアンダースコアのみ使用できます',
  },
} satisfies FormValidation;

各フィールドのバリデーションルールが詳細な型として保持されます。

typescript// userFormValidation.email の型は詳細に推論される
// {
//   required: true,
//   pattern: RegExp,
//   message: string
// }

// バリデーションルールを動的に取得
const emailRule = userFormValidation.email;
if (emailRule.required) {
  console.log('メールアドレスは必須項目です');
}

// pattern プロパティも型安全にアクセス可能
if (emailRule.pattern) {
  const isValid = emailRule.pattern.test(
    'test@example.com'
  );
  console.log(isValid); // true
}

バリデーションルールの不備も検知されます。

typescript// エラー例:不正なプロパティ
const invalidValidation = {
  email: {
    required: true,
    minLenght: 5, // typo: minLength ではない
  },
} satisfies FormValidation;
// Error: Object literal may only specify known properties,
// and 'minLenght' does not exist in type 'ValidationRule'

実例 6:テーマ設定

デザインシステムのテーマ設定を定義する実例です。

typescript// テーマの型定義
type Theme = {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
  };
  spacing: {
    small: number;
    medium: number;
    large: number;
  };
  typography: {
    fontFamily: string;
    fontSize: {
      small: string;
      medium: string;
      large: string;
    };
  };
};

satisfies を使って、ライトテーマとダークテーマを定義します。

typescript// ライトテーマ
const lightTheme = {
  colors: {
    primary: '#3b82f6',
    secondary: '#8b5cf6',
    background: '#ffffff',
    text: '#1f2937',
  },
  spacing: {
    small: 8,
    medium: 16,
    large: 24,
  },
  typography: {
    fontFamily: "'Inter', sans-serif",
    fontSize: {
      small: '12px',
      medium: '16px',
      large: '24px',
    },
  },
} satisfies Theme;

ダークテーマも同様に定義できます。

typescript// ダークテーマ
const darkTheme = {
  colors: {
    primary: '#60a5fa',
    secondary: '#a78bfa',
    background: '#1f2937',
    text: '#f9fafb',
  },
  spacing: {
    small: 8,
    medium: 16,
    large: 24,
  },
  typography: {
    fontFamily: "'Inter', sans-serif",
    fontSize: {
      small: '12px',
      medium: '16px',
      large: '24px',
    },
  },
} satisfies Theme;

テーマの各プロパティが詳細な型として保持されます。

typescript// lightTheme.colors.primary の型は "#3b82f6"(リテラル型)
// darkTheme.colors.primary の型は "#60a5fa"(リテラル型)

// テーマごとに異なる色が型レベルで区別される
const lightPrimary: '#3b82f6' = lightTheme.colors.primary;
const darkPrimary: '#60a5fa' = darkTheme.colors.primary;

// spacing も詳細な型が保持される
const mediumSpacing: 16 = lightTheme.spacing.medium;

テーマ設定の不備も検知されます。

typescript// エラー例:必須プロパティの欠落
const incompleteTheme = {
  colors: {
    primary: '#3b82f6',
    secondary: '#8b5cf6',
    // background と text が欠けている
  },
  spacing: {
    small: 8,
    medium: 16,
    large: 24,
  },
} satisfies Theme;
// Error: Type is missing the following properties from type 'Theme':
// typography, background, text

satisfies の高度な活用法

as const との組み合わせ

satisfiesas const を組み合わせることで、さらに厳密な型推論が可能になります。

typescript// ステータスコードの定義
type HttpStatus = {
  code: number;
  message: string;
};

type StatusMap = {
  [key: string]: HttpStatus;
};

as constsatisfies を併用します。

typescript// as const と satisfies を組み合わせ
const statusCodes = {
  ok: { code: 200, message: 'OK' },
  created: { code: 201, message: 'Created' },
  badRequest: { code: 400, message: 'Bad Request' },
  notFound: { code: 404, message: 'Not Found' },
} as const satisfies StatusMap;

// statusCodes.ok.code の型は 200(リテラル型)
// statusCodes.ok.message の型は "OK"(リテラル型)

この組み合わせにより、完全にイミュータブルかつ型安全なオブジェクトが作成されます。

typescript// すべてのプロパティが readonly かつリテラル型
const okCode: 200 = statusCodes.ok.code;
const okMessage: 'OK' = statusCodes.ok.message;

// 変更しようとするとエラー
statusCodes.ok.code = 201;
// Error: Cannot assign to 'code' because it is a read-only property

ユニオン型との組み合わせ

satisfies はユニオン型とも効果的に組み合わせられます。

typescript// ログレベルの型定義
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

type LogConfig = {
  level: LogLevel;
  timestamp: boolean;
  format: 'json' | 'text';
};

ユニオン型を使った設定を satisfies で検証します。

typescript// ログ設定の定義
const logConfig = {
  level: 'info',
  timestamp: true,
  format: 'json',
} satisfies LogConfig;

// logConfig.level の型は "info"(リテラル型)
// LogLevel 型のサブタイプとして扱われる

型の絞り込みも正確に機能します。

typescript// level に応じた処理の分岐
if (
  logConfig.level === 'debug' ||
  logConfig.level === 'info'
) {
  console.log('詳細ログを出力');
}

// format に応じた処理
if (logConfig.format === 'json') {
  console.log('JSON 形式で出力');
}

ジェネリクスとの組み合わせ

satisfies はジェネリック型とも組み合わせて使用できます。

typescript// ジェネリック型の定義
type ApiEndpoint<T> = {
  url: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  responseType: T;
};

// レスポンスデータの型定義
type UserData = {
  id: string;
  name: string;
};

ジェネリック型を使ったエンドポイント定義を satisfies で検証します。

typescript// エンドポイント設定
const getUserEndpoint = {
  url: '/api/users',
  method: 'GET',
  responseType: {} as UserData,
} satisfies ApiEndpoint<UserData>;

// getUserEndpoint.method の型は "GET"(リテラル型)
// getUserEndpoint.url の型は "/api/users"(リテラル型)

型パラメータを活用した柔軟な定義が可能になります。

satisfies と他の型システム機能の比較

satisfies vs 型注釈

型注釈と satisfies の違いを比較表で整理します。

#項目型注釈(: Typesatisfies備考
1型チェックどちらも型チェックを実施
2リテラル型の保持satisfies は詳細な型を保持
3型の過剰検知satisfies の方が厳密
4型推論satisfies は推論を活用
5使用シーン明示的な型指定型検証+推論目的に応じて使い分け

コード例で比較してみましょう。

typescripttype Point = { x: number; y: number };

// 型注釈
const p1: Point = { x: 10, y: 20 };
// p1.x の型は number(リテラル型ではない)

// satisfies
const p2 = { x: 10, y: 20 } satisfies Point;
// p2.x の型は 10(リテラル型)

satisfies vs 型アサーション(as)

型アサーションと satisfies の違いを比較表で整理します。

#項目型アサーション(as Typesatisfies備考
1型チェックsatisfies は厳密にチェック
2型の不足検知as は検知しない
3型の過剰検知as は検知しない
4安全性satisfies の方が安全
5使用シーン型の強制変換型の検証原則 satisfies を推奨

コード例で比較してみましょう。

typescripttype User = { id: string; name: string; email: string };

// 型アサーション(エラーにならない)
const u1 = { id: '123', name: '山田' } as User;
// email が欠けているが、コンパイルエラーにならない

// satisfies(エラーになる)
const u2 = { id: '123', name: '山田' } satisfies User;
// Error: Property 'email' is missing

まとめ

satisfies 演算子は、TypeScript 4.9 で導入された画期的な機能で、型の安全性と型推論の柔軟性を両立させます。従来の型注釈や型アサーションでは実現できなかった「型の過剰チェック」と「型の不足チェック」を同時に行いながら、詳細な型情報(リテラル型など)を保持できるのです。

本記事で紹介した実例から、以下のような場面で satisfies が特に有効であることがわかりました。

  • 設定オブジェクト:API 設定、環境変数、テーマ設定など
  • ルーティング:Next.js などのルート定義
  • バリデーション:フォームのバリデーションルール
  • 定数定義:カラーパレット、ステータスコード、エラーメッセージなど

satisfies を活用することで、実行時エラーを未然に防ぎ、より堅牢な TypeScript コードを書けるようになります。型安全性を保ちながらも、型推論の恩恵を最大限に受けられるこの機能を、ぜひ実務で活用してください。

今後は as const satisfies のような組み合わせ技も積極的に使い、完全にイミュータブルかつ型安全なコードを目指していきましょう。

関連リンク