【TypeScript】as constの使い方!型を固定する実務テクニック

【TypeScript】as constの使い方!型を固定する実務テクニック

PR Amazonのアソシエイトとして、ITナレッジライフは適格販売により収入を得ています。

記事の文字数:4,293

TypeScriptのas const(constアサーション)の使い方を徹底解説。リテラル型推論や再帰的なreadonly化の仕組み、オブジェクトや配列からユニオン型を自動生成する実務パターン、Object.freezeとの違いまでコード例付きで分かりやすく紹介します。

ITエンジニアにお勧めの本 ↗

おすすめ

エンジニアが知っておきたい思考の整理術 複雑な情報を【理解する】【伝える】テクニック

難易度
実用性
図解

思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。

ベストセラー

世界一流エンジニアの思考法

難易度
実用性
思考法

技術スキル以上に大切な「考え方」をアップデートできる。世界基準の効率的な仕事術が学べます。

文系社会人のためのIT資格の歩き方: 文系のあなたに最適な道をご提案します (YKcreate kiki)

難易度
実用性
キャリア度

ロードマップが非常に明確。キャリアプランを見据えた、無駄のない学習順序を提案してくれます。

エンジニアを説明上手にする本 相手に応じた技術情報や知識の伝え方

難易度
実用性
コミュ力

「伝える技術」もエンジニアの重要なスキル。相手の視点に立った、具体的な説明のコツが満載です。

技術広報入門 ー テックブログから始めるエンジニアカルチャーのつくり方

難易度
実用性
独自性

テックブログを入口にエンジニア組織の文化をどう設計し、育てていくかという実践論が学べます。

TypeScriptTypeScript [タイプスクリプト]JavaScriptに型システムを追加した言語でオブジェクトや配列を定義した際、意図したリテラル型ではなく string や number などの広い型(プリミティブ型)に推論されて困った経験はないでしょうか。

例えば、関数の引数に特定の文字列しか渡せない設計にしているのに、オブジェクトのプロパティを渡そうとすると「型 ‘stringstring [ストリング]文字列を表すデータ型’ を型 ‘“primary” | “secondary”’ に割り当てることはできません」というコンパイルエラーが発生することがあります。

このような場面で役立つのが as const(constアサーション) です。本記事では、as const の基本概念や仕組み、実務で頻出する活用テクニック、さらには JavaScript の Object.freeze との違いや注意点まで、サンプルコードを交えて詳しく解説します。

TypeScriptの as const(constアサーション)とは?

as const は、TypeScript 3.4 で導入された constアサーション(const assertion) と呼ばれる機能です。値の末尾に as const を記述することで、TypeScriptコンパイラに対して「この値をこれ以上変化しない固定の値として型推論してほしい」と指示できます。

なぜ as const が必要なのか?(リテラル型のワイドニング問題)

TypeScriptでは、変数を let やオブジェクトのプロパティとして定義すると、後から値が変更される可能性を考慮して、型を広く推論する挙動( 型のリテラル・ワイドニング / Literal Widening )が働きます。

// 1. const で宣言したプリミティブ変数
const theme = "dark"; // 型: "dark"(リテラル型)
// 2. let で宣言したプリミティブ変数
let mode = "dark"; // 型: string(ワイドニングされる)
// 3. const で宣言したオブジェクト
const userConfig = {
theme: "dark", // 型: string(オブジェクトのプロパティは変更可能なためワイドニングされる)
maxRetry: 3, // 型: number
};

上記コードの userConfig.theme は、変数自体は const で宣言されているものの、オブジェクトのプロパティである userConfig.theme = "light" のような再代入が可能です。そのため、TypeScriptは型を "dark" ではなく string として推論します。

この挙動により、特定のリテラル型を要求する関数に値を渡せなくなる問題が生じます。

type ThemeMode = "light" | "dark";
function applyTheme(theme: ThemeMode) {
console.log(`テーマを ${theme} に変更しました`);
}
// コンパイルエラー:
// Argument of type 'string' is not assignable to parameter of type 'ThemeMode'.
applyTheme(userConfig.theme);

ここで as const を使うと、コンパイラに対してプロパティの値が不変であることを伝え、リテラル型のまま維持させることができます。

const 変数宣言と as const の決定的な違い

混同しやすいのが、JavaScriptの const 変数宣言 と TypeScriptの as const の違いです。

  • const 変数宣言: JavaScriptの構文であり、変数そのものへの再代入を禁止します。ただし、オブジェクトの内部プロパティや配列要素の変更までは防げません。
  • as const: TypeScript独自の型システム上の構文であり、値全体を リテラル型かつ読み取り専用(readonly) として推論させます。

JavaScriptの変数宣言については JavaScriptのvar・let・constの違い でも解説していますが、TypeScriptの型システムで真の不変性を表現するには as const が不可欠です。

as const を付与したときの3つの変化

値に as const を付与すると、型推論に以下の3つの変化が生じます。

  1. プリミティブ値がリテラル型になる: string や number ではなく、"dark" や 3 そのものが型になります。
  2. オブジェクトのプロパティが再帰的に readonly になる: すべてのプロパティに readonly 修飾子が付与され、再代入が禁止されます。
  3. 配列が readonly なタプル型(固定長配列)になる: 可変長の配列 T[] ではなく、順序と要素数が固定された readonly [T1, T2, ...] に推論されます。

as const の基本的な使い方

as const の具体的な書き方と、各データ構造における推論結果の変化を見ていきましょう。

1. オブジェクトへの適用

定数オブジェクトの末尾に as const を付与します。

const CONFIG = {
endpoint: "https://api.example.com",
timeout: 5000,
features: {
darkMode: true,
},
} as const;

このとき、推論される型は以下のようになります。

// 推論される型:
// {
// readonly endpoint: "https://api.example.com";
// readonly timeout: 5000;
// readonly features: {
// readonly darkMode: true;
// };
// }

注目すべき点は、ネストされた features のプロパティまで 再帰的に readonly になることです。プロパティを書き換えようとすると、コンパイル時に即座にエラーが検知されます。

// コンパイルエラー: Cannot assign to 'timeout' because it is a read-only property.
CONFIG.timeout = 10000;

2. 配列への適用

配列リテラルの末尾に as const を付与すると、可変長配列ではなく 読み取り専用タプル型 に変化します。

// 通常の配列定義
const normalColors = ["red", "green", "blue"];
// 型: string[]
// as const を付与した配列定義
const readonlyColors = ["red", "green", "blue"] as const;
// 型: readonly ["red", "green", "blue"]

readonlyColors に対して push や pop、インデックス指定での要素書き換えを行うコードは、すべてコンパイルエラーとして検出されます。

// コンパイルエラー: Property 'push' does not exist on type 'readonly ["red", "green", "blue"]'.
readonlyColors.push("yellow");
// コンパイルエラー: Cannot assign to '0' because it is a read-only property.
readonlyColors[0] = "pink";

3. アングルブラケット構文(<const>)との比較

as const は、以下のようにアングルブラケット構文(<const>)を用いて記述することも可能です。

// アングルブラケット構文
const roles = <const>["admin", "editor", "viewer"];

どちらの構文もコンパイル結果は同一ですが、ReactReact [リアクト]Meta開発のUIライブラリなどの JSX / TSX 環境ではタグ構文(<const>)と衝突して構文エラーになる恐れがあります。そのため、現場の実務コードでは末尾に置く as const 記法で統一することが推奨 されます。

実務で役立つ as const の活用パターン

ここからは、実務の TypeScript 開発で高頻度に使われる as const の強力な実践パターンを紹介します。

パターン1: 配列からユニオン型を自動生成する

実務で最も重宝されるのが、定数配列からリテラルのユニオン型を導出するテクニックです。

// 選択肢のマスター配列を定義
export const ORDER_STATUS = ["pending", "shipped", "delivered", "canceled"] as const;
// 配列からユニオン型を生成
export type OrderStatus = (typeof ORDER_STATUS)[number];
// 型: "pending" | "shipped" | "delivered" | "canceled"

もし as const がないと、typeof ORDER_STATUS[number] の結果は単なる string になってしまいます。as const を付けることで、マスターデータとなる配列を1箇所定義するだけで、対応する型定義を二重管理することなく安全に生成できます。

パターン2: 定数オブジェクトからキーや値のユニオン型を生成する(Enumの代替)

TypeScriptの enum は便利な反面、トランスパイル後にJavaScriptの即時実行関数が生成されたり、数値Enumでの意図しない挙動などの問題が指摘されることがあります。

そのため、現在のモダンな開発では as const オブジェクトを用いた「Enum代替パターン」が広く採用されています。

// 定数オブジェクトを定義
export const HTTP_STATUS = {
OK: 200,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
} as const;
// キーのユニオン型: "OK" | "BAD_REQUEST" | "UNAUTHORIZED" | "NOT_FOUND" | "INTERNAL_SERVER_ERROR"
export type HttpStatusKey = keyof typeof HTTP_STATUS;
// 値のユニオン型: 200 | 400 | 401 | 404 | 500
export type HttpStatusCode = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS];

このパターンを使うと、JavaScriptとしては軽量なプレーンオブジェクトのまま、型安全なキーや値のアクセスと入力補完を実現できます。

パターン3: Reactカスタムフックの戻り値タプルを固定する

Reactの useState のように、カスタムフックから [値, 更新関数] のようなタプル形式で返却したい場合、as const が必須となります。

import { useState } from "react";
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue((prev) => !prev);
// as const を付与しない場合: (boolean | (() => void))[] と推論されてしまう
// as const を付与することで: readonly [boolean, () => void] に推論される
return [value, toggle] as const;
}
// 呼び出し側の利用例
function App() {
const [isOpen, toggleOpen] = useToggle();
// isOpen は boolean 型、toggleOpen は () => void 型として正確に認識される
}

もし as const を付けないと、TypeScriptは配列の各要素が混ざったユニオン型の配列((boolean | (() => void))[])と推論してしまい、インデックスで取得した値が関数なのか真偽値なのか特定できなくなってしまいます。

パターン4: 設定オブジェクトのキー補完とリテラル制約の両立

設定ファイルやルーティング定義などで、特定のキーに合致するリテラル型のみを許可したい場合にも有効です。

const ROUTE_CONFIG = {
HOME: "/",
USERS: "/users",
USER_DETAIL: "/users/:id",
SETTINGS: "/settings",
} as const;
type RoutePath = (typeof ROUTE_CONFIG)[keyof typeof ROUTE_CONFIG];
function navigateTo(path: RoutePath) {
console.log(`Navigating to ${path}`);
}
navigateTo(ROUTE_CONFIG.HOME); // OK
navigateTo("/dashboard"); // コンパイルエラー: 登録されていないパスは弾かれる

関連する型の絞り込みや検証については、TypeScriptの型ガードの使い方 も併せて参考にしてください。

as const を使う際の注意点と落とし穴

非常に便利な as const ですが、実務で扱う上で知っておくべき注意点がいくつか存在します。

1. 実行時の不変性は保証されない(Object.freeze との違い)

最も重要なポイントは、 as const はTypeScriptのコンパイル時のみに作用する型システム上の機能 であり、コンパイル後のJavaScriptコードには何も出力されないという点です。

実行時(ランタイム)にオブジェクトを書き換えから保護したい場合は、JavaScript標準の Object.freeze を使用する必要があります。

項目as constObject.freeze()
適用スコープコンパイル時(TypeScript)実行時(JavaScriptエンジン)
JS出力コード完全消滅(影響なし)メソッド呼び出しが残る
プロパティ再代入コンパイルエラーになる実行時にエラー(strictモード時)
ネストの深さ再帰的に適用(ディープ)直下のプロパティのみ(シャロー)
型推論の変化リテラル型かつ readonly一般的な型推論のまま(追加型定義が必要)
// as const の場合(型レベルでの保護)
const configA = { port: 3000 } as const;
// コンパイルエラーになるが、JS実行時に追加のオーバーヘッドはない
// Object.freeze の場合(実行時レベルでの保護)
const configB = Object.freeze({ port: 3000 });
// 実行時にプロパティが凍結される(ただし浅い階層のみ)

大規模なアプリケーションやライブラリ開発で、外部のJavaScriptコードからの予期せぬ破壊的変更を防ぎたい場合は、両者を組み合わせて Object.freeze({ ... } as const) と定義することも有効な手法です。

2. ミュータブルな関数引数に渡すとエラーになる

as const を付けたオブジェクトや配列は readonly になるため、引数として可変(ミュータブル)な配列やオブジェクトを要求する関数には直接渡せません。

function sortNumbers(numbers: number[]) {
return numbers.sort();
}
const scores = [80, 95, 60] as const;
// コンパイルエラー:
// The type 'readonly [80, 95, 60]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
sortNumbers(scores);

このエラーを解決するには、関数側の引数の型を readonly number[] に変更するか、呼び出し側でスプレッド構文 [...scores] を使って新しい可変配列のコピーを作成して渡す必要があります。

// 対策1: スプレッド構文でコピーを作成して渡す
sortNumbers([...scores]);
// 対策2: 関数側で readonly を受け入れる設計にする
function safeSortNumbers(numbers: readonly number[]) {
return [...numbers].sort();
}
safeSortNumbers(scores); // OK

3. 変数全体の代入には const、値の固定には as const を使い分ける

変数そのものを再代入させたくない場合は通常の const 宣言を使い、オブジェクトの中身や配列の要素まで厳格に型を固定したい場合に as const を組み合わせるのが原則です。

不必要にすべてのオブジェクトに as const を付けてしまうと、後から状態を変更したい処理で余計な型エラーが発生する原因になります。用途が「マスターデータ」「固定設定」「アクション名の定義」など、本当に不変であるべきデータに絞って適用しましょう。

まとめ

本記事では、TypeScriptにおける as const(constアサーション)の基本動作から実務的な活用パターン、注意点までを解説しました。

  • as const は値をリテラル型かつ再帰的な readonly として型推論させる機能
  • 配列リテラルに付与すると固定長かつ読み取り専用のタプル型になる
  • 定数配列から (typeof ARRAY)[number] でリテラルのユニオン型を自動導出できる
  • 定数オブジェクトから typeof OBJ[keyof typeof OBJ] で値のユニオン型を生成できる
  • 実行時の不変性は保護されないため、実行時の保護が必要な場合は Object.freeze を併用する

as const を適切に活用することで、型の多重定義やワイドニングによる意図しない不具合を未然に防ぎ、保守性の高い堅牢なコードベースを構築できます。ぜひ日々の開発で役立ててください。


以上で本記事の解説を終わります。
よいITライフを!
ITナレッジライフ 運営者
この記事を書いた人

Z (ITナレッジライフ)

現役のITエンジニア。Linux、プログラミング、IT用語など、日々の業務で得た「痒いところに手が届く」技術情報を発信しています。

プロフィールと編集ポリシーを見る →

ITエンジニアにお勧めの本 ↗

おすすめ

エンジニアが知っておきたい思考の整理術 複雑な情報を【理解する】【伝える】テクニック

難易度
実用性
図解

思考の整理というテーマを、実務ですぐに使える具体的な「技術」として落とし込んだ一冊です。

人気記事


記事を評価

Thanks!
目次
Scroll to Top