前言
在 TypeScript 裡,as const 是一個很常見的語法。
例如:
const DIRECTIONS = [
'north',
'south',
'east',
'west',
] as const;
前面已經用了 const,最後再多加了一個 as const。
原因在於兩者作用於不同層面:
const是 JavaScript 的宣告:它鎖定的是「變數綁定」,確保該變數不能被重新賦值;as const則是 TypeScript 的型別斷言,影響的是「型別推導」。它會要求 TypeScript 以最精確的方式推導值的型別,保留其字面量型別(Literal Type),並將 object 或 array 視為唯讀(Readonly)。
const 跟 as const 的區別
一個普通的 const:
const direction = 'north';
這裡 TypeScript 可以推斷出 direction 是:
"north"
但如果是 array:
const directions = ['north', 'south', 'east', 'west'];
TypeScript 通常會推斷成:
string[]
也就是說,TypeScript 認為這是一個「可以放任何 string 的 array」。
所以這是合法的:
directions.push('apple');
即便我們只想讓這個 array 應該只包含幾個固定選項。
這時候就可以使用 as const:
const DIRECTIONS = [
'north',
'south',
'east',
'west'
] as const;
TypeScript 會把它推斷成:
readonly [
'north',
'south',
'east',
'west'
]
這時候:
DIRECTIONS.push('apple');
就會直接出現 TypeScript error。
as const 的作用
as const 主要有三個作用:
1. 保留字面量型別(Literal Type),避免型別寬化(Type Widening)
在沒有使用 as const 時,TypeScript 會根據一般使用情境進行型別推導,將字面量值(Literal Value)推導為較寬鬆的型別(Widened Type)。
例如:
const direction = 'north';
// 型別:string
雖然實際值是 ’north’,但 TypeScript 會推導為 string,表示未來可能是任何字串。
使用 as const 後:
const direction = 'north' as const;
// 型別:'north'
TypeScript 會保留最精確的型別資訊,將其推導為字面量型別(Literal Type),表示該值只能是 ’north’,而不能是其他字串。
2. 將物件屬性轉為唯讀(Readonly Properties)
const 僅限制變數本身不能被重新賦值,並不會影響 object 內部屬性的可變性。
例如:
const user = {
name: 'Alice',
};
user.name = 'Bob'; // OK
加入 as const 後:
const user = {
name: 'Alice',
} as const;
user.name = 'Bob'; // Error
TypeScript 會自動為所有屬性加上 readonly,使 object 結構成為不可變(Immutable)的型別。
其推導結果等同於:
{
readonly name: 'Alice';
}
3. 將陣列推導為唯讀元組(Readonly Tuple)
一般 array 會被推導為可變長度的 array 型別:
const directions = ['north', 'south'];
// 型別:string[]
string[] 代表:
- 長度可變
- 元素可修改
- 可使用 push()、pop() 等方法
- directions.push(’east’); // OK
使用 as const 後:
const directions = ['north', 'south'] as const;
型別會變成:
readonly ['north', 'south']
此時:
- 長度固定
- 順序固定
- 元素不可修改
- 無法使用會改變內容的方法
- directions.push(’east’); // Error
因此,as const 不僅讓 array 成為唯讀,也將其從一般 array 提升為具有固定結構的元組。
總得來說,as const 是讓 TypeScript 不要進行寬鬆推導,而是完整保留這個值當下最精確、不可變的型別資訊。
最實用的用途:建立 Union Type
這是 as const 很常被使用到的地方。
假設我們有一組固定的 direction:
export const DIRECTIONS = [
'north',
'south',
'east',
'west',
] as const;
接著可以從這個 array 直接產生 TypeScript type:
export type Direction = (typeof DIRECTIONS)[number];
最後 Direction 就會變成:
type Direction =
| 'north'
| 'south'
| 'east'
| 'west';
因此我們可以寫:
function setDirection(direction: Direction) {
// ...
}
setDirection('south'); // OK
setDirection('west'); // OK
setDirection('apple'); // Error
這帶來了極大的方便,因為我們只需要維護一份資料。
如果沒有這個做法,就會需要寫成:
const DIRECTIONS = [
'north',
'south',
'east',
'west',
];
type Direction =
| 'north'
| 'south'
| 'east'
| 'west';
這樣就有兩份需要同步維護的資料。
如果未來新增:
'northeast'
就必須記得同時修改 array 和 type。
而使用 as const ,則可以讓 type 自動跟著 array 改變:
const DIRECTIONS = [
'north',
'south',
'east',
'west',
] as const;
type Directions = (typeof DIRECTIONS)[number];
那 (typeof DIRECTIONS)[number] 是什麼?
來看到這個寫法:
(typeof DIRECTIONS)[number]
拆解看,首先:
typeof directions
會取得 DIRECTIONS 的 type:
readonly [
'north',
'south',
'east',
'west',
]
接著:
[number]
可以理解成:
取得這個 array 裡所有元素的 type。
因此最後得到:
'north'
| 'south'
| 'east'
| 'west';
也就是我們想要的 Union Type。
as const 的作用域僅限於 Type-level
Type-level 是 TypeScript 中的核心概念。
簡單來說:它是只存在於「寫程式與編譯階段」的邏輯世界,完全不會產生任何 JavaScript 執行期(Runtime)程式碼。
在 TypeScript 中:
Type-level → 編譯後徹底消失 (不影響執行效能)
Value-level → 轉換成 JavaScript 留下來 (在瀏覽器/Node.js 執行)
在 TypeScript 中,as const 常被誤認為具備 Runtime Immutable 的效果。然而,TypeScript 的型別約束與 JavaScript 的執行期行為必須嚴格區分:
as const的本質:指示 TypeScript 編譯器將該值推導為最精確的字面值型別(Literal Type),並加上readonly修飾符。編譯為 JavaScript 後,as const不會產生任何語法或機制。淺層唯讀與引用漏洞:當
as const作用於包含外部引用的物件時,無法保證底層資料的不可變性。
例如:
cconst arr = [1, 2, 3];
const obj = {
items: arr,
} as const;
// 1. Type-level 限制:無法將新的 reference 指派給 obj.items
obj.items = []; // Error: Cannot assign to 'items' because it is a read-only property.
// 2. Runtime 行為:原始 array 仍可被操作,導致 obj.items 的內容改變
arr.push(4); // 成功執行,obj.items 實質上變為 [1, 2, 3, 4]
因此,若需要型別安全與精確型別推導,使用 as const;而要在執行期防止篡改(Runtime Immutability),則需結合原生 JavaScript 的 Object.freeze() 或使用第三方 Immutable 函式庫。
適合使用 as const 的場景
通常會在「固定選項」這類場景使用 as const。
例如:
const APPOINTMENT_STATUSES = [
'pending',
'confirmed',
'completed',
'cancelled',
'no_show',
] as const;
或:
const BOOKING_MODES = [
'online',
'line',
'phone',
] as const;
也可以使用 object:
const ROLES = {
ADMIN: 'admin',
STAFF: 'staff',
CUSTOMER: 'customer',
} as const;
這時候每個 value 都會保留成 literal type。這種寫法也可以拿來建立類似 enum 的 pattern。TypeScript 官方文件也展示了使用 as const 建立 enum-like object 的方式。
總結
const 與 as const 分別作用於不同的層級:const 負責 JavaScript 執行期的 Value-level,as const 則作用於 TypeScript 編譯期的 Type-level。
透過 as const,我們可以將 object 與 array 宣告為最嚴格的 readonly 字面值型別。進一步結合 typeof 操作符(如 type Direction = (typeof DIRECTIONS)[number]),即可直接利用資料來源反推 Union Type。這樣只需維護一份資料,省去手動同步的繁瑣,並徹底避免改漏出錯的風險。