【入門編】引数に渡すオブジェクトの「satisfies」演算子による型安全と推論精度の両立 – TypeScript コア・型システムの基礎解析バイブル

こんにちは!フロントエンドからバックエンドまで、TypeScriptの深淵を日々旅しているチーフアーキテクトの先輩です。

TypeScriptを使っていると、「関数の引数に渡す設定オブジェクトの型を厳格にチェックしたいけれど、そうするとプロパティの具体的な型が消えてしまって困る……」という壁にぶつかったことはありませんか?例えば、リテラル型の情報が `string` や `number` に丸められてしまって、補完が効かなくなったり……。

そんな悩みを一発で解決してくれるのが、TypeScript 4.9で導入された `satisfies` 演算子です。

ここをクリアすれば、型安全と推論精度の両立という「TypeScriptの美味しいところ」を完全に使いこなせるようになりますよ。さっそく、その極意を一緒に見ていきましょう!

—

1. 従来のやり方でぶ壁:型安全をとるか、推論をとるか

まずは、よくあるシチュエーションを想像してください。
ある関数に、画面のテーマ設定(フォントサイズやカラー)を渡すケースを考えてみます。

// 設定オブジェクトの型定義
type ThemeConfig = {
primaryColor: string;
fontSize: number | string;
};

// 実際に渡すオブジェクト
const myTheme = {
primaryColor: “#007acc”,
fontSize: 16,
} satisfies ThemeConfig; // あ、先走っちゃいました。まずは従来の方法から!

もし、従来の型注釈(Colon `:`)を使うと、こうなります。

type ThemeConfig = {
primaryColor: string;
fontSize: number | string;
};

// 【パターンA】型注釈(: ThemeConfig)を使う場合
const myTheme: ThemeConfig = {
primaryColor: “#007acc”,
fontSize: 16,
};

// ここで問題発生!
// myTheme.fontSize は ThemeConfig で定義された `number | string` に抽象化されてしまうため、
// 実際の値が `16`(数値)であったという情報がコンパイラから失われています。

イメージ図で表すと、こういう状態です。

【型注釈 (:) の場合】
書いた値: { primaryColor: “#007acc”, fontSize: 16 (number) }
↓
型注釈の壁: 「ThemeConfig の型に合わせなさい!」
↓
結果の型: { primaryColor: string, fontSize: number | string } ← 具体的な値の型が消える!

「プロパティのタイポや型違いは防ぎたいけれど、自分が書いた具体的な値の型(リテラル型など)はそのまま維持してほしい!」
そんなワガママを叶えてくれるのが `satisfies` 演算子なんです。

—

2. `satisfies` 演算子の基本:型を満たしつつ、推論を維持する

では、主役の登場です。`satisfies` は、「このオブジェクトは指定した型を満たしている(satisfies)か?」をコンパイラに検証させつつ、オブジェクトが持つ本来の細かい型推論をそのまま保持する魔法の演算子です。

実際のコードで動きを見てみましょう。

type RouteConfig = {
path: string;
// パラメータを持つパスか、静的なパスかなどを定義したいとする
handler: () => void;
};

// ルート設定のレコード型(キーは任意の文字列、値は RouteConfig)
type AppRoutes = Record;

// satisfies を使った書き方
const routes = {
home: {
path: “/”,
handler: () => console.log(“Home”),
extraInfo: “welcoming page”, // ← 独自のプロパティを追加してもOK!
},
about: {
path: “/about”,
handler: () => console.log(“About”),
},
} satisfies AppRoutes;

// ここがすごいのポイント!
// 1. AppRoutes の型を満たしているかチェックされるため、
// もし handler を書き忘れたりしたら、ちゃんとコンパイルエラーになります。
// 2. それでいて、routes.home.extraInfo という独自のプロパティにもアクセスできます!
console.log(routes.home.extraInfo); // 「”welcoming page”」と推論され、エラーにならない!

どう評価されているのか?(コンパイラ的視点)

`satisfies` を使ったとき、TypeScriptの型チェッカーは内部で次のような検証を行っています。

1. 検証フェーズ: 右側のオブジェクトが、左側の型(`AppRoutes`)の構造を完全に満たしているかをチェックする。ここでタイポや型ミスマッチがあれば即座にコンパイルエラーになります。
2. 推論フェーズ: 型注釈のように型を「上書き(強制)」するのではなく、オブジェクトが本来持っているリテラル型や追加プロパティの構造をそのまま保持した状態で、変数に型を割り当てます。

—

3. 関数引数でどう活きる?実践的なコード例

それでは、今回のテーマである「関数に渡す設定オブジェクト」での実践的な使い方を見ていきましょう。

例えば、HTTPリクエストをラップしたカスタムクライアントがあって、そこに渡すオプションの型安全を高めたい場面を想像してください。

type RequestOptions = {
method: “GET” | “POST” | “PUT” | “DELETE”;
headers?: Record;
timeout?: number;
};

// 設定を受け取って処理する関数
function sendRequest(url: string, options: RequestOptions) {
// 処理のロジック…
console.log(`Sending ${options.method} to ${url}`);
}

// 呼び出し時に satisfies を使う!
sendRequest(“https://api.example.com/users”, {
method: “GET”,
headers: {
“Content-Type”: “application/json”,
},
timeout: 5000,
// ここで仮に `retries: 3` などの未定義プロパティを書くと、
// satisfies を使っていれば「そんなプロパティは RequestOptions に無いよ!」と教えてくれます。
} satisfies RequestOptions);

さらに、一度変数として定義してから関数に渡すケースでは、`satisfies` の真価が爆発します。

// あらかじめ設定オブジェクトを定義
const userCreateConfig = {
method: “POST”,
headers: {
“Authorization”: “Bearer token_abc123”,
},
// メソッドの型が “POST” というリテラル型として保持されている
} satisfies RequestOptions;

// 関数に渡す
sendRequest(“https://api.example.com/users”, userCreateConfig);

もしここで `satisfies` の代わりに型注釈(`: RequestOptions`)を使ってしまうと、`method` の型が `”GET” | “POST” | “PUT” | “DELETE”` という広い型に抽象化されてしまいますが、`satisfies` を使えば `”POST”` というピンポイントな型が維持されるため、型安全の精度が一段と上がります。

—

4. 初学者が陥りがちな文法エラーと注意点

ここで、`satisfies` を使う際につまづきやすいポイントをいくつか押さえておきましょう。

① 記述する位置の勘違い

`satisfies` は、値の後ろ、セミコロンやカンマの前に書きます。

// ❌ やってしまいがちなエラー(型注釈のつもりでコロンの代わりに書くなど)
const config: satisfies RequestOptions = { … };
// SyntaxError: Unexpected token

// ⭕️ 正しい書き方(値の直後に置く)
const config = { … } satisfies RequestOptions;

② 「部分的な型チェック」はできない(オブジェクト全体に対する検証)

`satisfies` はオブジェクト全体に対して型を検証します。「一部のプロパティだけ `satisfies` を使って、残りは適当に…」ということはできません。必ずオブジェクトの全体構造が対象の型を満たしている必要があります。

—

まとめ:`satisfies` でコードの知性を一段引き上げよう

今回は、関数引数や設定オブジェクトにおける `satisfies` 演算子の活用法について解説しました。

  • 型注釈( `:` ): 型を強制する代わりに、具体的な値の型情報が失われる。
  • satisfies 演算子: 型を満たしていることを保証しつつ、具体的な型情報や追加プロパティを完璧に維持する。

この違いをマスターすると、TypeScriptの型システムがより一層あなたの味方になってくれるはずです。
ここをクリアできれば、もうTypeScriptの基本はバッチリマスターできていますよ!

日々の開発で、「このオブジェクト、型チェックは厳しくしたいけど推論は落としたくないな…」と思ったら、ぜひ `satisfies` を思い出してくださいね。それでは、快適なTypeScriptライフを!

タイトルとURLをコピーしました