satisfies 演算子でできること

satisfies は TypeScript 4.9 で追加された演算子だ。型アノテーションとは異なり、値の型を維持しつつ、特定の型に適合するかをチェックできる。従来の型アノテーションでは実現できなかった柔軟な型付けが可能になる。

型アノテーションの限界

まず、従来の型アノテーションの問題点を見てみよう。

type Colors = Record<string, string>;

const colors: Colors = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff",
};

// colors.red は string 型
// colors.purple も string 型(存在しないのにエラーにならない)
const red = colors.red;  // string
const purple = colors.purple;  // string(実行時は undefined)

Record<string, string> と型を指定すると、任意のキーでアクセスできてしまう。存在しないキーへのアクセスもエラーにならない。

satisfies の登場

satisfies を使うと、型チェックを行いつつ、より具体的な型を維持できる。

type Colors = Record<string, string>;

const colors = {
  red: "#ff0000",
  green: "#00ff00",
  blue: "#0000ff",
} satisfies Colors;

// colors.red は "#ff0000" 型(リテラル型)
// colors.purple はエラー
const red = colors.red;  // "#ff0000"
// const purple = colors.purple;  // エラー: Property 'purple' does not exist

satisfies は「この値は Colors 型の条件を満たすが、推論された型をそのまま使う」という意味だ。

型アノテーション(: Type)

指定した型に強制される。推論された型情報は失われる。

satisfies

指定した型に適合するかチェックされる。推論された型は維持される。

よくあるユースケース

設定オブジェクトの型チェック

type Config = {
  port: number;
  host: string;
  debug: boolean;
};

const config = {
  port: 3000,
  host: "localhost",
  debug: true,
} satisfies Config;

// config.port は 3000(リテラル型)
// 型アノテーションだと number になる

許可された値のチェック

type Theme = "light" | "dark" | "system";

const themeConfig = {
  default: "light",
  current: "dark",
} satisfies Record<string, Theme>;

// default と current が Theme 型であることを保証
// かつ、themeConfig.default は "light" 型として推論される

関数のオブジェクト

type EventHandlers = Record<string, (...args: any[]) => void>;

const handlers = {
  onClick: (e: MouseEvent) => console.log(e),
  onKeyDown: (e: KeyboardEvent) => console.log(e),
} satisfies EventHandlers;

// handlers.onClick は (e: MouseEvent) => void として推論される
// 型アノテーションだと (...args: any[]) => void になってしまう

as const との組み合わせ

satisfiesas const を組み合わせると、さらに強力になる。

const routes = {
  home: "/",
  about: "/about",
  contact: "/contact",
} as const satisfies Record<string, string>;

// routes.home は "/" 型(リテラル型かつ readonly)
// 型チェックも行われる

as const で完全に readonly なリテラル型にしつつ、satisfies で構造のチェックを行う。

as const のみ

リテラル型として固定されるが、型チェックは行われない。タイポがあっても検出できない。

satisfies のみ

型チェックは行われるが、as const ほど厳密なリテラル型にはならない。

両方使う

リテラル型として固定され、かつ型チェックも行われる。最も厳密。

エラー検出の例

satisfies は値の構造が型に適合しない場合、具体的なエラーを出す。

type Config = {
  port: number;
  host: string;
};

const config = {
  port: "3000",  // エラー: Type 'string' is not assignable to type 'number'
  host: "localhost",
} satisfies Config;

型アノテーションでも同じエラーは出るが、satisfies を使うと推論される型が維持される点が異なる。

注意点

satisfies はチェックのみを行い、型を変換しない。

const value = "hello" satisfies string;
// value の型は "hello"(リテラル型)

const value2: string = "hello";
// value2 の型は string

型を広げたい場合は型アノテーションを使い、推論を維持しつつチェックしたい場合は satisfies を使う。目的に応じて使い分けよう。

satisfies は比較的新しい機能だが、設定オブジェクトやルーティング定義など、「構造は決まっているが具体的な値も保持したい」場面で威力を発揮する。積極的に活用していきたい。