satisfiesは、設定値が型を満たすか確かめながら、各プロパティの具体的な型を使いたいときに役立ちます。ただし「何を付けても推論は一切変わらない」「余分なキーを必ず拒否する」と覚えると、実際の診断と食い違います。
同じオブジェクトを型注釈、satisfies、asで宣言し、生成された.d.tsとエラーを比べました。結論を先に置くと、今回の表示設定では次の違いがありました。
| 書き方 |
readyを読むときの型 |
waitingの欠落 |
|---|---|---|
: Notices |
`string | number[]` |
satisfies Notices |
string |
エラー |
as Notices |
`string | number[]` |
推論は補完表示だけで判断しない
検証環境は2026年9月11日、macOS arm64、Node.js 24.15.0、TypeScript 7.0.2です。strict: true、target: ES2022、module: ESNext、moduleResolution: Bundlerで、型検査とdeclaration出力を実行しました。値は比較用に作った表示設定です。
type Notices = Record<"ready" | "waiting", string | number[]>;
export const annotated: Notices = { ready: "Ready", waiting: [1, 2] };
export const checked = { ready: "Ready", waiting: [1, 2] } satisfies Notices;
export const asserted = { ready: "Ready", waiting: [1, 2] } as Notices;
console.log(checked.ready.toUpperCase());
checked.ready.toUpperCase()は通り、実行結果はREADYでした。annotated.readyへ同じメソッド呼び出しを書くと、配列の可能性が残るためTS2339です。
生成された宣言を見ると、呼び出し可否の理由が分かります。
type Notices = Record<"ready" | "waiting", string | number[]>;
export declare const annotated: Notices;
export declare const checked: {
ready: string;
waiting: number[];
};
export declare const asserted: Notices;
export {};
checkedには各プロパティの型が残り、他の2つはNoticesという注釈の型になります。satisfiesなら許容する値の種類をまとめて検査し、個別の値を使う箇所で余分な絞り込みをせずに済みます。TypeScript 4.9の説明でも、式の型を扱いやすいまま適合性を調べる用途が示されています。
asを検査の代わりにすると欠落が残る
type Notices = Record<"ready" | "waiting", string | number[]>;
const value = { ready: "Ready" } as Notices;
console.log(value.waiting);
このコードは型検査を通り、undefinedを出力しました。同じ値をsatisfies Noticesへ変えると、waiting欠落のTS2741になります。asでプロパティが作られるわけではありません。
一方、asなら任意の変換が全部通る、という意味でもありません。TypeScriptは型同士の関係によってアサーションを拒否します。ここで確認したのは、必須キーの欠落があるこの具体例を通すことです。型アサーションの公式説明にあるとおり、実行時の値の検査や変換にはなりません。
「推論を変えない」には文脈がある
export const plain = { enabled: true, count: 1 };
export const checked = { enabled: true, count: 1 } satisfies { enabled: boolean; count: number };
export const frozenType = { enabled: true, count: 1 } as const;
この宣言出力では、plain.enabledはboolean、checked.enabledはtrueでした。countはどちらもnumberです。対象型から受ける文脈によって、リテラルの扱いまで完全に同一とは限りません。
as constを付けたfrozenTypeでは、enabledがreadonlyなtrue、countがreadonlyな1になりました。これも実行時にオブジェクトを凍結する操作ではありません。設定をあとで書き換える設計なら、trueに狭まったプロパティへfalseを入れたいのか、最初からbooleanを受け入れたいのかを分けて考えます。
余分なキーを拒否する範囲
{ ready: "Ready", waiting: [1], typo: 1 } satisfies Noticesを直接書くと、余分なtypoにTS2353が出ました。ところが、その値を先に変数へ入れると結果が変わります。
type Notices = Record<"ready" | "waiting", string | number[]>;
const input = { ready: "Ready", waiting: [1], typo: 1 };
const value = input satisfies Notices;
console.log(value.typo);
こちらは通り、1を出力します。必要なプロパティを持つ値として適合しても、余分なプロパティを取り除きません。外部データのキーを制限したい場合は、型構文とは別に実行時の検査が必要です。
この実験で選ぶ基準は、ローカルな設定の誤りを検出し、値ごとの操作を残したいならsatisfies、変数に持たせる型を明示したいなら型注釈です。asは、その型だと判断できる別の根拠がある箇所に限って使います。
再現コードのT02ケースと実行記録には、成功した宣言と意図的に失敗させた入力の両方を残しました。診断番号や推論結果を別バージョンへそのまま一般化せず、更新時は宣言出力も比べてください。