字面量类型
本教程共 80 篇 · 第 16 篇 · 更新于 2026-08-10 · 约 10 分钟阅读
本节目标:掌握字符串、数字、布尔三种字面量类型的用法,理解
const和as const对类型推断的影响,学会用联合字面量替代枚举,以及模板字面量类型的动态生成。
字面量类型是什么
前面我们学的类型——string、number、boolean——是”宽泛类型”。string 可以是任何字符串,number 可以是任何数字。但有时候你需要的不是”任何”,而是”那几个”。
字面量类型(literal type)让类型缩小到具体的值。它告诉 TypeScript:“这个变量不只是一般的 string,它只能是这几个特定的字符串”。
// 宽泛类型:能接受任何字符串
let name: string = "小明";
name = "小红"; // ✅
// 字面量类型:只能接受特定值
let direction: "up" | "down" = "up";
// direction = "left"; // ❌ "left" 不在 "up" | "down" 中
字符串字面量类型
最常见也最实用的字面量类型。把变量限制为几个特定的字符串:
// 直接写在注解里
let status: "pending" | "active" | "completed";
status = "pending"; // ✅
// status = "done"; // ❌
// 单独抽成类型别名(推荐)
type Status = "pending" | "active" | "completed";
function updateStatus(newStatus: Status) {
console.log(`状态更新为: ${newStatus}`);
}
updateStatus("active"); // ✅
// updateStatus("waiting"); // ❌
编辑器的自动补全会列出所有选项——你甚至不用记有哪些值。这在定义 API 参数、组件 props、配置选项时极其好用。
数字字面量类型
跟字符串字面量同理,不过是把数字锁死在特定值上:
// 骰子点数:只能是 1 到 6
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4; // ✅
// roll = 7; // ❌ 7 不是有效的骰子点数
// HTTP 状态码
type SuccessCode = 200 | 201 | 204;
type ErrorCode = 400 | 401 | 403 | 404 | 500;
type StatusCode = SuccessCode | ErrorCode;
function checkStatus(code: StatusCode): string {
if (code === 200) return "OK";
if (code === 404) return "Not Found";
return String(code);
}
数字字面量在需要把返回值限定为特定数值时很有用——比如比较函数返回 -1 | 0 | 1。
function compare(a: number, b: number): -1 | 0 | 1 {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
布尔字面量类型
布尔只有两个值,所以布尔字面量类型就是 true 和 false。实际上 TypeScript 的 boolean 就是 true | false 的别名。
单独使用 true 或 false 作为类型不常见,但它可以用于区分重载或限制返回:
// 这个函数只能返回 true
function alwaysTrue(): true {
return true;
// return false; // ❌
}
// 跟字面量联合一起用
type Result = true | "success" | 200;
const 和 as const:让 TypeScript”看紧一点”
用 let 声明的变量推断为宽泛类型,用 const 声明的则推断为字面量类型。这个行为我们在 08 章提过,现在展开讲:
let name = "小明"; // 类型: string(宽泛)
const name2 = "小明"; // 类型: "小明"(精确)
let score = 95; // 类型: number
const score2 = 95; // 类型: 95
const 的类型收窄只对顶层有效。对象和数组的属性仍然是宽泛类型:
const user = {
name: "小明", // 类型: string(不是 "小明")
age: 25 // 类型: number(不是 25)
};
// user.name 仍然是 string——你可以 user.name = "小红"(如果对象不是 const 的话)
要让整个对象/数组都变成只读的字面量类型,用 as const 断言:
const user = {
name: "小明",
age: 25
} as const;
// user 的类型现在是:
// {
// readonly name: "小明";
// readonly age: 25;
// }
// user.name = "小红"; // ❌ 只读属性不能修改
// user.age = 30; // ❌ 只读属性不能修改
as const 同时干三件事:
- 把属性类型收窄为字面量(
string→"小明") - 给所有属性加上
readonly - 把数组变为只读元组
数组用 as const 的效果:
const colors = ["red", "green", "blue"] as const;
// 类型: readonly ["red", "green", "blue"]
// colors[0] = "yellow"; // ❌ 只读,不能修改
// colors.push("purple"); // ❌ 没有 push 方法
这在需要精确类型时非常有用——比如把 as const 数组传给需要字面量联合类型的函数:
const methods = ["GET", "POST", "DELETE"] as const;
type Method = typeof methods[number]; // "GET" | "POST" | "DELETE"
联合字面量替代枚举
第 12 章提到了用联合字面量替代枚举。这里给一个完整对比:
// 枚举写法
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
// 联合字面量写法(推荐)
type Direction2 = "UP" | "DOWN" | "LEFT" | "RIGHT";
// 两种写法的使用感几乎一样
function move(dir: Direction2) {
console.log(`移动方向: ${dir}`);
}
move("UP"); // ✅ 编辑器自动补全
// move("up"); // ❌ 大小写不对也会报错
联合字面量的优势已经在枚举那章说过:零运行时开销、更简洁、不需要 import。如果你的常量只在一个文件内用或者值本身足够说明含义,联合字面量是更好的选择。
模板字面量类型
TypeScript 4.1 引入了模板字面量类型,可以用模板字符串语法构建新的字符串类型。这在需要动态拼接字符串类型的场景中非常强大:
// 基础用法:拼接字面量
type EventName = `on${string}`;
let event: EventName = "onClick"; // ✅
// let event2: EventName = "click"; // ❌ 不以 "on" 开头
// 联合类型分发:组合多个值
type Direction = "north" | "south" | "east" | "west";
type Distance = "1km" | "5km" | "10km";
type Route = `${Direction}-${Distance}`;
// 自动生成所有组合:
// "north-1km" | "north-5km" | "north-10km" |
// "south-1km" | ...(共 12 种)
let myRoute: Route = "east-5km"; // ✅
// let badRoute: Route = "north-2km"; // ❌ 2km 不在 Distance 中
配合内置的字符串工具类型,可以做大小写转换:
// Capitalize: 首字母大写
type Greeting = "hello" | "goodbye";
type FormalGreeting = Capitalize<Greeting>; // "Hello" | "Goodbye"
// Uppercase / Lowercase
type Shout = Uppercase<"hello">; // "HELLO"
type Whisper = Lowercase<"HELLO">; // "hello"
// 组合使用
type Method = "get" | "post" | "delete";
type HandlerName = `handle${Capitalize<Method>}`;
// "handleGet" | "handlePost" | "handleDelete"
模板字面量类型适合生成有规律的字符串类型——事件名、CSS 单位、API 路径等。但对于极其复杂的动态字符串,模板字面量类型会让编译变慢,适度使用。
可运行示例
// 字面量类型综合示例
// 1. 联合字面量替代枚举
type LogLevel = "debug" | "info" | "warn" | "error";
function log(level: LogLevel, message: string) {
let prefix: string;
switch (level) {
case "debug": prefix = "🔍"; break;
case "info": prefix = "📝"; break;
case "warn": prefix = "⚠️"; break;
case "error": prefix = "❌"; break;
}
console.log(`${prefix} [${level.toUpperCase()}] ${message}`);
}
log("info", "服务启动成功");
log("warn", "内存使用率超过 80%");
// 2. as const:深度只读
const CONFIG = {
host: "localhost",
port: 8080,
env: "development"
} as const;
// CONFIG.port = 3000; // ❌ 只读,不能改
console.log(`服务器: ${CONFIG.host}:${CONFIG.port} (${CONFIG.env})`);
// 3. as const 数组 → 联合类型
const SIZES = ["sm", "md", "lg"] as const;
type Size = typeof SIZES[number]; // "sm" | "md" | "lg"
function render(size: Size) {
console.log(`渲染尺寸: ${size}`);
}
render("md"); // ✅
// render("xl"); // ❌
// 4. 模板字面量类型
type Prefix = "btn" | "input" | "card";
type Variant = "primary" | "secondary" | "danger";
// 自动生成 class 名称
type BEMClass = `${Prefix}--${Variant}`;
// "btn--primary" | "btn--secondary" | "btn--danger" |
// "input--primary" | ...
let className: BEMClass = "card--danger";
console.log(`CSS 类名: ${className}`);
输出:
📝 [INFO] 服务启动成功
⚠️ [WARN] 内存使用率超过 80%
服务器: localhost:8080 (development)
渲染尺寸: md
CSS 类名: card--danger
小结
字面量类型是 TypeScript 类型精确度的终极体现——从”这是个数字”细化到”这只能是 1、2 或 3”。联合字面量替代枚举是新项目的推荐做法,as const 让普通对象和数组获得只读的字面量类型,模板字面量类型则把字符串拼接带入了类型层面。到这里,基础类型部分全部结束。你已经掌握了 TypeScript 最核心的类型工具——从最简单的 string/number 到 never/unknown,再到联合、交叉和字面量类型。接下来可以进入更高级的类型特性了。