TypeScript satisfies演算子で型安全性と型推論を両立する方法

問題

TypeScriptでオブジェクトに型を指定する場合、通常はこのように書きます。

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

const config: Config = {
  api: "https://api.example.com",
  port: 3000,
  debug: true,
};

問題は、明示的に型を指定すると、TypeScriptが値を広い型で推論してしまうことです。config.apiの型がstringになってしまい、オートコンプリートが弱くなります。

as constを使うとreadonlyになって後から変更できませんし、as Configは型チェックをスキップするので危険です。

解決方法

satisfies演算子を使います。

const config = {
  api: "https://api.example.com",
  port: 3000,
  debug: true,
} satisfies Config;

// config.apiの型: "https://api.example.com"(リテラル型!)
// config.portの型: 3000
// さらにConfig型との整合性も検証されます

実務でよく使うパターンはルート設定です。

type Route = {
  path: string;
  method: "GET" | "POST" | "PUT" | "DELETE";
};

const routes = {
  getUsers: { path: "/users", method: "GET" },
  createUser: { path: "/users", method: "POST" },
  updateUser: { path: "/users/:id", method: "PUT" },
} satisfies Record<string, Route>;

// routes.getUsers.methodの型: "GET"(stringではありません!)

asアサーションとの違いは明確です。

// asは嘘をつくことが可能(危険)
const bad = { api: 123 } as Config; // エラーなし!

// satisfiesは実際に検証する
const good = { api: 123 } satisfies Config; // エラー発生!

ポイント

  • satisfiesは型検証と狭い型推論を同時に実現します
  • asは型アサーションなのでミスを見逃す可能性がありますが、satisfiesは実際の値を検証します
  • 設定オブジェクト、ルートマップ、テーマカラーなどの定数オブジェクトに特に有用です
  • TypeScript 4.9から利用可能です