TypeScriptのas const|型が広がる罠とenum代替・注意点
TypeScriptで as const を使えば、緩く広がってしまう型をリテラル型のまま固定できます。const で定数を宣言したのに、オブジェクトのプロパティが string に広がって思った型にならない、という経験はないでしょうか。
const で書いたのに、なぜプロパティの型が string に広がってしまうの?
as const で十分なのか毎回迷う。
こうした「型が広がる」「定数の表現方法に迷う」といったモヤモヤは、as const(constアサーション)の挙動を一度押さえれば解消できます。値をリテラル型に固定する仕組みと、配列・オブジェクトでの推論結果、enum の代替としての使い方、そして使いすぎたときの落とし穴まで分かるので、定数まわりの型で迷わず手を動かせるようになります。
この記事は次のような方におすすめです。
- 基本型は理解したが、型推論が
stringなどに広がってしまう挙動に困っている方 - 定数オブジェクトや配列を厳密なリテラル型に固定したい方
- enum を使うべきか
as constで代替すべきか判断基準がほしい方 satisfiesやreadonlyとの使い分けまで実務目線で整理したい方
読み終えるころには、as const が型をどう変えるのかを推論結果で説明でき、enum・ユニオン型との選び分けや注意点を踏まえて実装できるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
4年間ずっと年収260万だったエンジニアが、転職で510万になるまでの全記録
as constとは?constアサーション(const assertion)の基本
as const は、値を「これ以上広げず、リテラル型のまま・読み取り専用として扱う」ことをコンパイラに伝える constアサーション(const assertion)です。通常の型推論では値が広い型へと拡大されますが、as const を付けるとその拡大を止め、書いたとおりの具体的なリテラル型に固定されます。
let a = "success"; // 型: string
const b = "success"; // 型: "success"(リテラル型)
const c = { kind: "success" }; // 型: { kind: string }(プロパティは広がる)
const d = { kind: "success" } as const; // 型: { readonly kind: "success" }
注意したいのは、これは変数宣言の const/let(再代入できるかどうか)とは別の仕組みだという点です。変数宣言の const は「変数への再代入」を禁止しますが、c のようにオブジェクトを代入すると中身のプロパティ型は string に広がります。一方 as const は値そのものの型に作用し、プロパティまでリテラルに固定します。
なお、同じ as というキーワードでも、型を別の型として扱わせる型アサーション(値 as 型)とは目的が異なります。見た目が似ているぶん混同しやすいので注意しましょう。
リテラル型と型のwidening(型の拡大)
普通の const で型が広がり、as const で止まる理由は「型の widening(拡大)」という推論ルールにあります。widening とは、"a" のような具体的な値から string のような広い型へと、コンパイラが自動的に型を一般化する挙動のことです。
リテラル型とは、"success" や 42、true のように「その値そのもの」を表す型です。文字列リテラル型・数値リテラル型・真偽値リテラル型があります。再代入できない const 変数では値が確定するためリテラル型のまま保たれますが、オブジェクトのプロパティや let 変数では後から別の値が入る可能性があるとみなされ、widening によって広い型へ拡大されます。
const x = "a"; // 型: "a"(変数の const なので widening されない)
let y = "a"; // 型: string(再代入を想定して widening)
const obj = { v: "a" }; // 型: { v: string }(プロパティは widening)
const obj2 = { v: "a" } as const; // 型: { readonly v: "a" }(widening を停止)
as const を付けると、この widening が抑えられ、プロパティも含めて "a" のままリテラル型として推論されます。値の集合を厳密に表したい場面で効いてきます。
as constの使い方:オブジェクトと配列での挙動
as const の効果はプリミティブな値だけでなく、オブジェクトや配列のリテラルに付けたときにより顕著になります。オブジェクトでは再帰的に読み取り専用かつリテラル型へ、配列では可変長の配列ではなくタプルへと推論が変わります。それぞれ具体的に見ていきましょう。
オブジェクトに付けた場合(再帰的にreadonly化+リテラル型化)
オブジェクトリテラルに as const を付けると、ネストした内側のプロパティまで再帰的に readonly かつリテラル型として推論されます。深い階層のオブジェクトもまとめて固定できます。
const config = {
a: 1,
b: { c: 2 },
} as const;
// 型: { readonly a: 1; readonly b: { readonly c: 2 } }
config.a = 10; // 例:TS2540 Cannot assign to 'a' because it is a read-only property.
// (TypeScriptバージョンにより文言が異なる場合あり)
ここで押さえておきたいのは、これがあくまで「オブジェクト/配列リテラルとして書いた部分の型推論」だという点です。リテラルとして記述した構造が深く readonly に推論されるのであって、外部から渡ってきた参照先のミュータブルな値まで固定されるわけではありません。また readonly は型レベル(コンパイル時)のチェックであり、実行時にオブジェクトを凍結する Object.freeze とは別物です。コンパイル時に再代入を弾くだけで、実行時の書き換えを物理的に防ぐものではない点に注意しましょう。
readonly 修飾子そのものや Readonly<T> ユーティリティ型の詳しい挙動を押さえたい場合は、専用の解説をあわせて確認すると理解が深まります。
配列に付けた場合(タプル化+readonly化)
配列リテラルに as const を付けると、要素数や順序が不定の配列型ではなく、長さと各要素の型が固定された読み取り専用のタプル型として推論されます。
const arr1 = [1, 2, 3]; // 型: number[]
const arr2 = [1, 2, 3] as const; // 型: readonly [1, 2, 3]
arr2.push(4); // 例:Property 'push' does not exist on type 'readonly [1, 2, 3]'.
// (readonly タプルでは破壊的メソッドが型から除かれる。文言は要確認)
number[] では push などの破壊的メソッドが使えますが、readonly [1, 2, 3] ではそれらが型から取り除かれ、要素の追加や書き換えを試みると型エラーになります。各要素が 1 | 2 | 3 のようにリテラルとして残るため、typeof arr2[number] と組み合わせれば配列からユニオン型を自動生成する定番パターンにつながります。
type Nums = typeof arr2[number]; // 型: 1 | 2 | 3
as constの実践的な使いどころ
実務で as const が効くのは、値の集合を厳密な型として扱いたい場面です。代表的なのが、設定値やマスタデータを定数オブジェクトとして持ち、そのキーや値からユニオン型を導き出すケースです。
const STATUS = {
active: "ACTIVE",
inactive: "INACTIVE",
} as const;
type StatusKey = keyof typeof STATUS; // 型: "active" | "inactive"
type StatusValue = typeof STATUS[StatusKey]; // 型: "ACTIVE" | "INACTIVE"
as const を付けないと値が string に広がり、StatusValue はただの string になってしまいます。as const と typeof/keyof を組み合わせることで、定数の値と型の二重管理をなくし、定数を一箇所変えれば型も追従する状態を作れます。ここでの keyof は、オブジェクト型からキーのユニオンを取り出す役割を担っています。
関数の引数をリテラルに固定したいときにも有効です。配列をそのまま渡すと string[] に広がってしまう場面で、as const を付ければ要素のリテラルを保ったまま渡せます。
function setRole(role: "admin" | "user") {}
const roles = ["admin", "user"] as const;
setRole(roles[0]); // roles[0] の型は "admin"
定数表現としてのas constの選び方(独自比較表+判断フロー)
定数の集合を表現する手段には、enum・ユニオン型(リテラル型の union)・as const オブジェクトがあります。それぞれ型安全性やランタイムへの影響が異なるため、用途に応じて選び分けます。
| 観点 | enum | ユニオン型(リテラル) | as const オブジェクト |
|---|---|---|---|
| 型安全 | 高い | 高い | 高い |
| ランタイムコスト | あり(実体コードを生成) | なし(型のみ) | 値オブジェクトは残る |
| 反復(イテレート)可否 | 列挙しやすい | 値だけでは反復不可 | Object.values 等で反復可 |
| tree-shaking | 効きにくい場合がある | 影響なし | 効きやすい |
| 推奨場面 | 名前空間付きの列挙が欲しいとき | 値の集合を型だけで表したいとき | 値と型を両立したいとき |
選び方の目安は次のとおりです。
- 値を実行時にも一覧・反復したい、かつ値と型を一元管理したい →
as constオブジェクト+typeof/keyofでユニオン生成 - 型として値の集合を表せれば十分で、実体は不要 → リテラル型のユニオン
- 列挙としての名前空間や、数値enumの双方向マッピングなど enum 固有の機能が要る → enum
enum については「常に避けるべき」と単純化はできず、名前空間や反復のしやすさが要件なら選択肢になります。一方で、多くの定数表現は as const +ユニオンで置き換えられます。
// Before: enum
enum Color { Red = "Red", Green = "Green", Blue = "Blue" }
// After: as const オブジェクト
const Color = { Red: "Red", Green: "Green", Blue: "Blue" } as const;
type Color = typeof Color[keyof typeof Color]; // "Red" | "Green" | "Blue"
ちなみにenum から as const へ置き換えた際のバンドルサイズの差分は以下のようになります。

as constの注意点と落とし穴
便利な as const にも、付けすぎると扱いづらくなる側面があります。主な落とし穴は次の3点です。
- 型が過剰に詳細になる(型爆発):大きなオブジェクトに付けると全プロパティがリテラル&
readonlyになり、型の表示や扱いが重くなる readonlyが邪魔をする:テストの Mock データなど一部を書き換えて使い回したい場面で、読み取り専用ゆえに代入できず再利用しづらいas(型アサーション)との混同:見た目が似ているため、型を強制変換するasと取り違えやすい
固定はしたいが型チェックも効かせたい、という場合は satisfies との併用が有効です。as const satisfies T の形にすると、リテラルへ固定しつつ、その値が型 T に適合しているかを検証できます。
type Theme = { color: string; size: number };
const theme = {
color: "#fff",
size: 16,
} as const satisfies Theme;
// theme は readonly なリテラル型のまま、かつ Theme への適合もチェックされる
as const だけだと型 Theme への適合は確認されず、satisfies だけだと widening が起きます。両者を組み合わせることで、固定と検証を両立できます。
よくある質問
Q1. constアサーションは型アサーションとどう違う?
型アサーション(値 as 型)は値を別の型として扱わせる宣言で、constアサーション(as const)は値をリテラル型かつ readonly に固定する宣言です。前者は型の付け替え、後者は型の固定と目的が異なります。as 単体の詳しい使い方は型アサーションの解説で確認できます。
Q2. as constを付けたオブジェクトの値は実行時にも変更できない?
いいえ、as const が付与するのは型レベル(コンパイル時)の readonlyです。コンパイル時に再代入を型エラーとして弾くだけで、実行時にオブジェクトを凍結するわけではありません。実行時にもトップレベルの書き換えを防ぎたい場合は Object.freeze、ネストした値まで防ぎたい場合は再帰的な deep freeze などを検討します。
Q3. 定数表現はどの基準で選ぶべき?
多くの場合は as const +ユニオン型が扱いやすく推奨です。ただし用途次第で条件が変わり、値を反復したい・名前空間が欲しいといった要件があれば enum が向く場面もあります。判断の詳細は本文の比較表とフローを参考にしてください。
Q4. ネストが深いオブジェクトもas constで全部固定される?
オブジェクト/配列リテラルとして書いた部分は再帰的に readonly 化されるため、深い階層も固定されます。ただし固定されるのはリテラルとして記述した構造の型であり、外部から渡された参照先のミュータブルな値まで型・実行時に凍結するわけではない点には注意しましょう。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – as constで型を固定する勘所
この記事では as const(constアサーション)の基本から実践的な使いどころ、注意点までを整理しました。
as constは値をリテラル型に固定し、オブジェクト/配列リテラルを再帰的にreadonly化するconstアサーション- 主用途は型の widening 回避・
typeof/keyofと組み合わせたユニオン型の自動生成・enum の代替 readonlyは型レベルのチェックで、実行時凍結(Object.freeze)とは別物- 使いすぎによる型爆発や再利用しづらさは、
as const satisfies Tの併用や部分的な適用で回避できる
定数まわりの型に迷ったら、まず as const でリテラルに固定し、必要に応じて enum やユニオンと比較して選ぶのが扱いやすい出発点になります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






