Article
🧩 TypeScriptのasを減らす:satisfiesとの使い分けを実例で整理する
TypeScriptを書いていると、型エラーを消すために as を使いたくなる場面があります。
ただし、as は便利な一方で、使い方によってはTypeScriptの型チェックを自分で弱めてしまいます。
一方、TypeScript 4.9で追加された satisfies は、「この値が指定した型を満たしているかチェックする」一方で、元の具体的な型推論をできるだけ維持するための演算子です。
この記事では、as、型注釈、satisfies、as const の違いを、実務で使いやすい形で整理します。
先に結論
迷ったときは、だいたい次の順番で考えると安全です。
- まずはTypeScriptの型推論に任せる
- オブジェクトが特定の型を満たすことを確認したいなら
satisfies - 変数そのものの型を固定したいなら型注釈
: Type - リテラル値をそのまま保持したいなら
as const - コンパイラより自分の方が型を詳しく知っている場合だけ
as
as は「型を検証する」というより、コンパイラに対して『この型として扱ってよい』と伝える機能です。
as は型チェックではなく型アサーション
TypeScript公式ドキュメントでは、as は Type Assertion(型アサーション) と呼ばれています。
例えば次のコードです。
const element = document.getElementById("canvas") as HTMLCanvasElement;
document.getElementById() の戻り値は、TypeScriptから見ると HTMLElement | null です。
しかし開発者が「このIDの要素は必ず HTMLCanvasElement だ」と分かっている場合、as HTMLCanvasElement と書くことで型を絞れます。
重要なのは、as が実行時の値を変換しているわけではないことです。
const value = "100" as unknown as number;
console.log(typeof value);
// "string"
TypeScript上では number として扱えても、JavaScriptとして実行された値は文字列のままです。
つまり as は、Javaのキャストのように値そのものを変換する処理ではありません。
TypeScript公式ドキュメントでも、型アサーションはコンパイル時に取り除かれ、実行時チェックを行わないと説明されています。
as で型エラーを消すのが危険な理由
例えばAPIレスポンスを扱うコードを考えます。
type User = {
id: string;
name: string;
};
const response = {
id: "123",
};
const user = response as User;
このコードはコンパイルできる場合がありますが、実際には name がありません。
console.log(user.name.toUpperCase());
実行時にはエラーになる可能性があります。
TypeScriptが本来教えてくれるはずだった不整合を、as によって開発者側で握りつぶしてしまった状態です。
そのため、次のようなコードをレビューで見たら、一度立ち止まって考える価値があります。
something as SomeType
本当にTypeScriptより詳しい情報を持っているのか、それとも単に型エラーを消したいだけなのか、という点です。
satisfies は「型を満たすか」をチェックする
ここで使いやすいのが satisfies です。
type Config = {
apiUrl: string;
retryCount: number;
};
const config = {
apiUrl: "https://example.com",
retryCount: 3,
} satisfies Config;
この場合、config が Config を満たしているかTypeScriptが検証します。
例えばプロパティを間違えるとエラーになります。
const config = {
apiUrl: "https://example.com",
retryCount: "3",
} satisfies Config;
retryCount は number である必要があるので、コンパイル時に検出できます。
as Config で強制的に型を合わせるのとは、目的が逆です。
// 「Configとして扱う」
const config1 = value as Config;
// 「Configを満たしているか確認する」
const config2 = value satisfies Config;
この違いを覚えておくと使い分けやすくなります。
型注釈と satisfies の違い
では、普通に型注釈を書けばよいのではないでしょうか。
type Status = "success" | "error";
type ApiConfig = {
status: Status;
retryCount: number;
};
const config: ApiConfig = {
status: "success",
retryCount: 3,
};
もちろん、この書き方も正しいです。
ただし型注釈を付けると、変数は指定した型として扱われます。
satisfies は、型との互換性をチェックしつつ、値から得られる具体的な型情報を維持しやすいのが特徴です。
TypeScript公式の例では、次のようなケースが紹介されています。
type Colors = "red" | "green" | "blue";
type RGB = [number, number, number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255],
} satisfies Record<Colors, string | RGB>;
palette.green.toUpperCase();
palette が Record<Colors, string | RGB> を満たすことをチェックしつつ、green が具体的に文字列であるという情報も利用できます。
これが satisfies のかなり便利なポイントです。
オブジェクトのキー漏れ・タイプミスを検出する
設定値や定数テーブルでは特に便利です。
type Environment = "development" | "staging" | "production";
const apiUrls = {
development: "http://localhost:3000",
staging: "https://stg.example.com",
production: "https://example.com",
} satisfies Record<Environment, string>;
例えば production を書き忘れるとTypeScriptが検出します。
const apiUrls = {
development: "http://localhost:3000",
staging: "https://stg.example.com",
} satisfies Record<Environment, string>;
逆に、存在しないキーを追加しても検出できます。
const apiUrls = {
development: "http://localhost:3000",
staging: "https://stg.example.com",
production: "https://example.com",
prodution: "https://typo.example.com",
} satisfies Record<Environment, string>;
設定ファイルのタイプミスは実行時まで気づきにくいので、こうした用途では satisfies と相性が良いです。
ReactやNext.jsでも使いやすい
例えば画面ごとの設定をまとめるケースです。
type PageName = "home" | "settings" | "profile";
type PageConfig = {
title: string;
requiresAuth: boolean;
};
const pages = {
home: {
title: "ホーム",
requiresAuth: false,
},
settings: {
title: "設定",
requiresAuth: true,
},
profile: {
title: "プロフィール",
requiresAuth: true,
},
} satisfies Record<PageName, PageConfig>;
新しいページをUnion型に追加したのに設定を追加し忘れた場合、コンパイル時に気づけます。
type PageName = "home" | "settings" | "profile" | "billing";
billing が pages に存在しなければエラーになるため、変更漏れを防ぎやすくなります。
as const とは役割が違う
satisfies と一緒によく見かけるのが as const です。
const config = {
mode: "production",
} as const;
通常、オブジェクトの mode は string と推論される場面があります。
as const を付けると、より具体的なリテラル型を保持できます。
// "production" として扱われる
config.mode;
さらにオブジェクトのプロパティは readonly 扱いになります。
一方 satisfies の目的は、対象が指定した型を満たすか検証することです。
type AppConfig = {
mode: "development" | "production";
};
const config = {
mode: "production",
} satisfies AppConfig;
必要に応じて組み合わせることもできます。
const routes = {
home: "/",
settings: "/settings",
} as const satisfies Record<string, string>;
ただし as const を付けると readonly になるため、「後から値を書き換えたいオブジェクト」では意図に合っているか確認が必要です。
外部データの検証には satisfies だけでは足りない
ここは重要です。
APIレスポンスや localStorage、ユーザー入力など、実行時に外部から入ってくる値は satisfies だけでは安全になりません。
例えば次のデータです。
const response = await fetch("/api/users/1");
const data = await response.json();
response.json() の中身は実行してみるまで分かりません。
こうした値は、Zodなどのスキーマバリデーションライブラリや、自前のType Guardを使って実行時にも検証する必要があります。
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) {
return false;
}
return (
"id" in value &&
typeof value.id === "string" &&
"name" in value &&
typeof value.name === "string"
);
}
TypeScriptの型情報は基本的にコンパイル時のものです。
「TypeScriptで型が付いている = APIから来る値も必ず正しい」という意味ではありません。
実務での使い分け
自分なら、次の基準で使い分けます。
型推論に任せる
特に制約を掛ける必要がない場合です。
const user = {
id: "123",
name: "Taro",
};
不要な型指定を増やさない方がシンプルです。
satisfies
設定オブジェクト、マッピング、定数テーブルなどで、構造をチェックしたい場合です。
const config = {
// ...
} satisfies Config;
型注釈 : Type
変数をその型として扱うこと自体に意味がある場合です。
const config: Config = {
// ...
};
as
DOM APIなど、TypeScriptが取得できない情報を開発者が確実に把握している場合です。
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
ただし null の可能性まで消してよいかは別途確認が必要です。
as const
文字列や数値を広い型にせず、リテラル型として保持したい場合です。
const methods = ["GET", "POST"] as const;
レビューで as を見つけたときのチェックポイント
as 自体が悪いわけではありません。
ただし、次の3点を確認すると危険な型アサーションを減らしやすくなります。
asを外すとどんな型エラーが出るか- そのエラーを本当に無視してよい根拠があるか
satisfies、型ガード、型設計の見直しで解決できないか
特に、次のような二重アサーションは注意が必要です。
value as unknown as User
TypeScriptの安全装置をかなり強く回避しているため、本当に必要な理由をコード上またはレビューで説明できる状態にした方がよいです。
まとめ
as と satisfies は似て見えますが、役割はかなり違います。
as:この値を指定した型として扱うようTypeScriptに伝えるsatisfies:この値が指定した型を満たしているか確認する- 型注釈:変数そのものを指定した型として扱う
as const:値をより具体的なリテラル型として保持する
型エラーが出たときにすぐ as を追加するのではなく、まず「なぜTypeScriptがエラーを出しているのか」を確認するだけでも、実行時バグを減らしやすくなります。
設定オブジェクトや定数テーブルなら、まず satisfies を試してみるのがおすすめです。