こんにちは、かつコーチです。
前回はunion型を安全に絞り込む「型ガード」を扱いました。
今回はその型ガードとも相性がいい、as const(値を変更不可・かつ最も狭い型として扱う指示)とリテラル型推論を紹介します。
「配列や設定オブジェクトを定義したのに、思ったより型が広く推論されて困った」という経験がある人は、まさにこの記事のテーマにぶつかっています。
as constとは?
TypeScriptの型推論はデフォルトで”広い”
まず、as const をつけない場合にTypeScriptがどう型推論するかを見てみます。
const status = "success";
// 型は string ではなく "success"(変数宣言時点ではリテラル型)
let statusLet = "success";
// 型は string(letは再代入できるため広く推論される)
const で宣言した文字列はリテラル型として推論されますが、これはあくまで単純な代入の場合の話です。
オブジェクトや配列になると、事情が変わってきます。
const config = {
status: "success",
code: 200,
};
// config.status の型は string、config.code の型は number
config はオブジェクトなので、プロパティの値は「あとから書き換えられるかもしれない」という前提で、"success" ではなく string、200 ではなく number という広い型に推論されます。
as constで型を”固定”する
as const を末尾につけると、この推論を「値そのまま」に固定できます。
const config = {
status: "success",
code: 200,
} as const;
// config.status の型は "success"、config.code の型は 200
すべてのプロパティが readonly になり、値もリテラル型として厳密に推論されるようになります。
「この値は絶対に変わらない設定情報だ」ということを、型のレベルでTypeScriptに伝えられるのが as const の役割です。
配列とas constの組み合わせ
タプル型として固定する
配列に as const をつけると、要素数や順序まで固定されたタプル型になります。
const rgb = [255, 0, 0];
// 型は number[](要素数も順序も保証されない)
const rgbConst = [255, 0, 0] as const;
// 型は readonly [255, 0, 0](要素数3、各要素の値まで固定)
as const なしの rgb は「数値がいくつか入った配列」としか型付けされませんが、as const をつけた rgbConst は「必ず3つの要素で、それぞれ255・0・0」という厳密な型になります。
定数配列からunion型を作る
as const の代表的な活用法が、配列からunion型を自動生成するテクニックです。
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = typeof ROLES[number];
// type Role = "admin" | "editor" | "viewer"
function checkPermission(role: Role): void {
console.log(`${role}の権限を確認します`);
}
checkPermission("admin"); // OK
checkPermission("owner"); // エラー:Argument of type '"owner"' is not assignable to parameter of type 'Role'.
typeof ROLES[number] は「ROLES 配列の要素の型」を取り出す書き方で、as const があるおかげで string ではなく "admin" | "editor" | "viewer" というunion型になります。
配列とtype定義が同じ場所にまとまるので、選択肢を追加するときに片方だけ直し忘れる、という事故も防げます。
つまずきやすいポイント:型が広がってエラーになる
定数オブジェクトをそのまま渡してハマった話
私が実際につまずいたのは、"success" | "error" を受け取る関数に、定数オブジェクトのプロパティを渡そうとしたときでした。
❌ Before:as constなしでオブジェクトを定義してエラーになった
type ResultStatus = "success" | "error";
function handleStatus(status: ResultStatus): void {
console.log(`ステータス: ${status}`);
}
const apiResult = {
status: "success",
data: [1, 2, 3],
};
handleStatus(apiResult.status);
// エラー:Argument of type 'string' is not assignable to parameter of type 'ResultStatus'.
自分の目には apiResult.status が明らかに "success" という文字にしか見えていなかったので、なぜ string 扱いされてエラーになるのか最初は理解できませんでした。
原因は、オブジェクトのプロパティ値がデフォルトで string という広い型に推論されていたことでした。
✅ After:as constで値を固定してから渡す
type ResultStatus = "success" | "error";
function handleStatus(status: ResultStatus): void {
console.log(`ステータス: ${status}`);
}
const apiResult = {
status: "success",
data: [1, 2, 3],
} as const;
handleStatus(apiResult.status); // OK:status は "success" というリテラル型
as const を1つ付け加えるだけで、apiResult.status の型が "success" に固定され、ResultStatus にそのまま渡せるようになりました。
この一件以来、「文字列や数値を決め打ちで扱う定数は、とりあえず as const をつけておく」という習慣がつきました。
応用・一歩先の使い方
satisfiesと組み合わせて安全性をさらに高める
as const は値を固定する一方、オブジェクトの形が正しいかまではチェックしてくれません。
そこで役立つのが satisfies 演算子です。
type Theme = {
primary: string;
secondary: string;
};
const theme = {
primary: "#1a73e8",
secondary: "#34a853",
} as const satisfies Theme;
console.log(theme.primary); // 型は "#1a73e8"(リテラル型のまま)
satisfies Theme を組み合わせることで、「Theme の形は満たしつつ、値の型はリテラル型のまま保つ」という両立が可能になります。
as Theme のようにキャストしてしまうと値の型が広がってしまいますが、satisfies ならその心配がありません。
enumの代替として使う
as const を使った定数オブジェクトは、enum の代替としてもよく使われます。
const HttpStatus = {
OK: 200,
NotFound: 404,
ServerError: 500,
} as const;
type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus];
// type HttpStatus = 200 | 404 | 500
function logStatus(code: HttpStatus): void {
console.log(`HTTPステータス: ${code}`);
}
logStatus(HttpStatus.OK); // OK
enum はコンパイル後のJavaScriptに余分なコードを生成することがありますが、as const オブジェクトは通常のオブジェクトなので、余計なコードを増やさずに同じような使い勝手を実現できます。
まとめ
この記事のポイント
- オブジェクトや配列のプロパティ値は、デフォルトでは
stringやnumberのように広く推論される as constをつけると、値がリテラル型として固定され、readonlyにもなるtypeof 配列 as const [number]で、配列の中身からunion型を自動生成できる- 定数として決め打ちで扱う文字列・数値は、
as constをつけておくとunion型への受け渡しでエラーになりにくい satisfiesと組み合わせると、値のリテラル型を保ったまま型の形もチェックできる
次に読むべき記事
型を固定する as const を理解したら、次はnullやundefinedを扱う際に登場する非nullアサーションを見ていきましょう。
→ 次の記事:非nullアサーション(!)を使うべき場面・避けるべき場面
