TypeScriptのコードを読んでいると、as const という見慣れない記述に出くわすことがあります。

as const って何をしているの? as はなんとなくわかるけど、const と合わせると意味がよくわからない。
オブジェクトに as const を付けているコードを見たんだけど、付けない場合と何が違うんだろう?
enumの代わりに as const を使う、って説明を読んだけど、なぜそっちのほうがいいの?

この記事では、as const(constアサーション)が何をしているのかを基礎から整理します。なぜ必要なのか、どんな効果があるのか、どんな場面で使うのかを順番に説明していきます。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • TypeScriptのコードで as const を見かけたが、何をしているかわからない人
  • オブジェクトや配列にどんな効果があるか知りたい人
  • enumの代わりに as const を使う書き方を理解したい人
  • 型推論がどう変わるのか、コード例でイメージしたい人

この記事を読むと、as const がなぜ必要か・どう動くかを理解でき、実際のコードに使えるようになります。

それでは、順を追って詳しく見ていきましょう!

まずは動画解説を観る

as constとは何か(constアサーション)

as const は、TypeScriptに「この値はリテラルのまま固定してほしい」と伝える記法です。正式名称は「constアサーション(const assertion)」といいます。

なぜそういう記法が必要なのかを理解するには、まず「widening(型の拡大)」という現象を知っておく必要があります。

そもそもwideningとは?

TypeScriptはデフォルトで、値から型を推論するときに「少し広めの型」に推論することがあります。これをwidening(型の拡大)といいます。

変数への代入をコード例で見てみましょう。

// constで宣言した変数 → リテラル型のまま推論される
const direction = "left";
// type: "left" ← "left"というリテラル型

// letで宣言した変数 → string型に拡大される(widening)
let direction = "left";
// type: string ← "left"ではなくstringになる

const で宣言した変数は値が変わらないので、TypeScriptは "left" というリテラル型のまま推論してくれます。これは問題なさそうに見えます。

しかし、オブジェクトのプロパティは const で宣言しても widening が起きます

// constで宣言していても、プロパティはwideningされる
const config = { direction: "left" };
// type: { direction: string } ← "left"ではなくstringになる

function move(dir: "left" | "right") {
  console.log(dir);
}

move(config.direction);
// エラー: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'

config.direction は明らかに "left" なのに、型は string として推論されるため、move 関数に渡せません。オブジェクトのプロパティは後から変更できてしまうので、TypeScriptは安全のために広い型で推論する、という動作になっています。

このwideningを防ぐために使うのが as const です。

as constを付けると型が固定される

as const を付けると、TypeScriptは「この値はリテラルのまま扱う」と認識します。

// as const を付けると型が固定される
const config = { direction: "left" } as const;
// type: { readonly direction: "left" } ← "left"のまま推論される

function move(dir: "left" | "right") {
  console.log(dir);
}

move(config.direction); // OK! "left" 型として認識されるのでエラーなし

付ける場所はオブジェクトや配列の末尾です。

const obj = { ... } as const; // オブジェクトの末尾
const arr = [ ... ] as const; // 配列の末尾
const value = "left" as const; // リテラル値の末尾

as constの3つの効果

as const を付けると、主に3つの変化が起きます。順番に見ていきましょう。

①オブジェクトのプロパティをreadonlyにする

as const を付けると、オブジェクトのプロパティが readonly(読み取り専用) になり、かつプロパティの型が文字列・数値ではなくリテラル型に変わります。

// as const なし
const theme = {
  color: "blue",
  size: 16,
};
// type: { color: string; size: number }

theme.color = "red"; // エラーにならない(変更できてしまう)

// as const あり
const theme = {
  color: "blue",
  size: 16,
} as const;
// type: { readonly color: "blue"; readonly size: 16 }

theme.color = "red"; // エラー: Cannot assign to 'color' because it is a read-only property

string だった型が "blue" に、number だった型が 16 に変わっています。これにより、設定値やマスターデータのような「変えてはいけないオブジェクト」を型の力で守れるようになります。

ネストしたオブジェクトにも再帰的に適用されます。

const config = {
  api: {
    host: "example.com",
    port: 8080,
  },
} as const;

// type: { readonly api: { readonly host: "example.com"; readonly port: 8080 } }

深い階層まで自動的にreadonlyとリテラル型が適用されます。

②配列をreadonlyタプル型にする

配列に as const を付けると、string[] のような配列型ではなく、要素の型と順番が固定されたreadonlyタプル型になります。

// as const なし
const colors = ["red", "green", "blue"];
// type: string[] ← 要素はどれもstringとして扱われる

// as const あり
const colors = ["red", "green", "blue"] as const;
// type: readonly ["red", "green", "blue"] ← 各要素のリテラル型が保持される

string[] では個々の要素が "red""green" かを型として区別できませんが、タプル型になることで要素ごとのリテラル型が保持されます。後ほど紹介するUnion型の生成にも役立ちます。

③リテラル型のwideningを防ぐ

変数の宣言以外でも、wideningが起きる場面があります。たとえば 関数の戻り値では、明示的に型を書かないとwideningされることがあります。

// 戻り値の型にwideningが起きる
function getDirection() {
  return "left";
  // 戻り値の型: string ← wideningされる
}

// as const で固定する
function getDirection() {
  return "left" as const;
  // 戻り値の型: "left" ← リテラル型のまま
}

こうした場面では、as const を値の末尾に付けることでwidening を防げます。

as constの実践的な使い場面

効果がわかったところで、よく使われる実践的なパターンを2つ紹介します。

enumの代わりに使う

TypeScriptには enum という定数をまとめる構文がありますが、as const を使ってオブジェクトで代替する書き方も広く使われています。

// enumを使う書き方
enum Direction {
  Left = "left",
  Right = "right",
  Up = "up",
  Down = "down",
}

// as constを使った代替
const Direction = {
  Left: "left",
  Right: "right",
  Up: "up",
  Down: "down",
} as const;

// どちらも同じように使える
function move(dir: string) {}
move(Direction.Left); // "left"

as const を付けることで、プロパティの値がリテラル型として固定されるため、定数グループとして機能します。

なかむぅ
なかむぅ
enum との使い分けについては、こちらの記事で詳しく解説しています。
TypeScriptのenumは非推奨?使い方と代わりの書き方をわかりやすく解説
TypeScriptのenumは非推奨?使い方と代わりの書き方をわかりやすく解説TypeScriptのenumは本当に非推奨なのか。基本の使い方から問題点、代替手段まで初心者にもわかりやすく整理して解説します。...

値からUnion型を作る

as consttypeof を組み合わせることで、オブジェクトや配列の値から自動的にUnion型を生成することができます。

const STATUS = ["active", "inactive", "pending"] as const;
// type: readonly ["active", "inactive", "pending"]

type Status = typeof STATUS[number];
// type: "active" | "inactive" | "pending"

typeof STATUS[number] は「STATUS の各インデックスにアクセスしたときの型」を意味します。as constがなければ STATUS の型は string[] になるため、このUnion型の生成はできません。

オブジェクトの値からUnion型を作ることもできます。

const Direction = {
  Left: "left",
  Right: "right",
  Up: "up",
  Down: "down",
} as const;

type DirectionType = typeof Direction[keyof typeof Direction];
// type: "left" | "right" | "up" | "down"

function move(dir: DirectionType) {
  console.log(dir);
}

move(Direction.Left); // OK
move("left");         // OK
move("diagonal");     // エラー: 型が合わない

値の一覧とそこから派生するUnion型を1か所にまとめて管理できるので、値を追加・変更したときにUnion型も自動的に追従します。

as constを使うときの注意点

as const には便利な点がある一方、押さえておくべき制約が2つあります。

直接書いたリテラルにしか付けられない

as const が付けられるのは、その場に直接書いた文字列・数値・真偽値・配列・オブジェクトに限られます。変数に格納した後や、式の計算結果全体には付けられません。

// OK: その場に直接書いた値に付ける
const config = { host: "localhost" } as const;
const status = "active" as const;

// NG: 変数を経由する
const obj = { host: "localhost" };
const config = obj as const; // エラー

// NG: 式全体には付けられない
const x = (Math.random() < 0.5 ? 0 : 1) as const; // エラー

// OK: 式の中の各リテラルに個別に付ける
const x = Math.random() < 0.5 ? (0 as const) : (1 as const);

「直接書いたこの値をそのまま固定する」という記法なので、宣言と同時に付けるのが基本です。

外部変数を参照しているプロパティの中身は変更できる

as const はオブジェクトリテラルのプロパティをreadonlyにしますが、そのプロパティが外部変数を参照している場合、その変数の中身まで凍結されるわけではありません。

const arr = [1, 2, 3];

const obj = {
  items: arr,
} as const;
// type: { readonly items: number[] }

obj.items = [];    // エラー: readonly なので再代入は不可
obj.items.push(4); // OK: arr が number[] のままなので push は通る

obj.items の再代入はreadonlyで防げますが、items が指している配列自体は number[] の型を保ったままです。そのため push のような変更操作は型レベルで許容されてしまいます。

なかむぅ
なかむぅ
リテラルとして直接書いた値であれば再帰的に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アサーション)は、値の型をリテラル型のまま固定する記法です
  • 普通にオブジェクトや配列を宣言すると widening(型の拡大)が起き、stringnumber に推論されてしまう場面があります
  • as const を付けることで次の3つの効果が得られます
    1. オブジェクトのプロパティが readonly になり、型がリテラル型になる
    2. 配列が readonly タプル型になり、各要素の型が保持される
    3. 関数の戻り値などでwideningを防げる
  • よく使われる実践パターンとして、enumの代替Union型の自動生成があります
  • ただし、直接書いたリテラルにしか付けられない点と、外部変数を参照しているプロパティの中身までは凍結されない点は覚えておきましょう

以上が as const の基本的な使い方と注意点です。「型が思ったより広く推論されてしまう」と感じた場面で、まず as const を試してみてください。

なかむぅ
なかむぅ
enumとas constのどちらを使うべきか迷ったときは、こちらの記事も参考にしてみてください。
TypeScriptのenumは非推奨?使い方と代わりの書き方をわかりやすく解説
TypeScriptのenumは非推奨?使い方と代わりの書き方をわかりやすく解説TypeScriptのenumは本当に非推奨なのか。基本の使い方から問題点、代替手段まで初心者にもわかりやすく整理して解説します。...
なかむぅ
なかむぅ
Union型と型ガードをもう少し深掘りしたいときは、こちらの記事も参考にしてみてください。
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法TypeScriptのユニオン型とは何かを基礎から解説。typeof・in・instanceofを使った型ガードの違いと使い分けも初心者向けにわかりやすく整理します。...

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。