TypeScript: TypeScript 联合类型与字面量类型
最后更新:2026-08-26
联合类型和字面量类型是 TypeScript 超越 JavaScript 的第一步——它们让你表达"这个值可以是A或B"和"这个值只能是这几个选项"。
1. 联合类型(Union Types)
联合类型用 | 连接多个类型,表示"值可以是这些类型中的任意一个":
(1) 基本语法
TYPESCRIPT
let id: number | string;
id = 42; // ✅ number 合法
id = "ABC"; // ✅ string 合法
id = true; // ❌ boolean 不在联合类型中
(2) 常见用途:函数参数
联合类型最常见的用途是让函数接受多种类型的参数:
TYPESCRIPT
function printId(id: number | string) {
console.log("你的ID是:" + id);
}
printId(42); // ✅
printId("ABC"); // ✅
printId(true); // ❌
(3) 联合类型的限制
联合类型的变量只能访问所有类型共有的成员:
TYPESCRIPT
function process(value: number | string) {
// value.toString() // ✅ number 和 string 都有 toString
// value.toUpperCase() // ❌ number 没有 toUpperCase
// value.toFixed(2) // ❌ string 没有 toFixed
}
📌 原因: TypeScript 必须保证在 value 是任何联合成员时代码都安全。只有所有成员都有的属性/方法才能直接调用。要访问特定类型的成员,需要先用"类型收窄"(见第3节)。
2. 字面量类型(Literal Types)
字面量类型把变量的值限制在特定的字面量上——不是"任何字符串",而是"只能是这几个字符串"。
(1) 三种字面量类型
| 字面量类型 | 语法 | 示例 |
|---|---|---|
| 字符串字面量 | "值1" | "值2" |
"north" | "south" | "east" | "west" |
| 数字字面量 | 1 | 2 | 3 |
0 | 1 | 2 |
| 布尔字面量 | true | false |
实际等于 boolean(只有两个值) |
(2) 字符串字面量类型
TYPESCRIPT
let direction: "north" | "south" | "east" | "west";
direction = "north"; // ✅
direction = "up"; // ❌ "up" 不在联合类型中
(3) 数字字面量类型
TYPESCRIPT
type HttpStatusCode = 200 | 301 | 404 | 500;
let code: HttpStatusCode = 200; // ✅
let code2: HttpStatusCode = 201; // ❌ 201 不在联合类型中
(4) 布尔字面量类型
TYPESCRIPT
type YesNo = true | false; // 等同于 boolean
type StrictTrue = true; // 只能是 true
💡 提示: 布尔字面量类型单独用意义不大(
true | false 就是 boolean),但在联合类型中配合其他类型时有用:string | true 表示"字符串或 true"。
▶ 示例:用字面量类型定义角色权限
TYPESCRIPT
type Role = "admin" | "editor" | "viewer";
function checkPermission(role: Role) {
if (role === "admin") {
console.log("管理员:完全权限");
} else if (role === "editor") {
console.log("编辑者:可编辑内容");
} else {
console.log("查看者:只读权限");
}
}
checkPermission("admin"); // ✅
checkPermission("editor"); // ✅
checkPermission("guest"); // ❌ 编译错误——"guest" 不是合法的 Role
输出:
TEXT
📖 仅展示
管理员:完全权限
编辑者:可编辑内容
3. 类型收窄(Type Narrowing)
联合类型的变量不能直接访问特定类型的成员,但可以通过"类型收窄"把范围缩小。
(1) typeof 收窄
TYPESCRIPT
function process(value: number | string) {
if (typeof value === "string") {
// 在这个分支里,TypeScript 知道 value 是 string
console.log(value.toUpperCase()); // ✅ string 的方法
} else {
// 在这个分支里,TypeScript 知道 value 是 number
console.log(value.toFixed(2)); // ✅ number 的方法
}
}
(2) 相等性收窄
TYPESCRIPT
function compare(a: string | number, b: string | boolean) {
if (a === b) {
// a 和 b 相等,它们的交集类型是 string
console.log(a.toUpperCase()); // ✅ 这里 a 一定是 string
}
}
(3) in 操作符收窄
TYPESCRIPT
function process(input: { name: string } | { age: number }) {
if ("name" in input) {
console.log(input.name); // ✅ 有 name 属性,推断为 { name: string }
} else {
console.log(input.age); // ✅ 没有 name,推断为 { age: number }
}
}
(4) instanceof 收窄
TYPESCRIPT
function processDate(value: Date | string) {
if (value instanceof Date) {
console.log(value.toISOString()); // ✅ Date 的方法
} else {
console.log(value.toUpperCase()); // ✅ string 的方法
}
}
▶ 示例:完整的类型收窄演示
TYPESCRIPT
function formatValue(value: number | string | boolean): string {
if (typeof value === "number") {
return "数字:" + value.toFixed(2);
} else if (typeof value === "string") {
return "字符串:" + value.toUpperCase();
} else {
return "布尔:" + value;
}
}
console.log(formatValue(3.14159));
console.log(formatValue("hello"));
console.log(formatValue(true));
输出:
TEXT
📖 仅展示
数字:3.14
字符串:HELLO
布尔:true
4. 联合类型 + 字面量类型的经典模式
联合类型和字面量类型配合,可以实现非常强大的类型约束。以下是三个经典模式:
(1) 模式一:可辨识联合(Discriminated Union)
用同一个属性的不同字面量值区分联合成员:
TYPESCRIPT
interface Circle {
kind: "circle"; // 可辨识属性
radius: number;
}
interface Rectangle {
kind: "rectangle"; // 可辨识属性
width: number;
height: number;
}
type Shape = Circle | Rectangle;
function getArea(shape: Shape): number {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2; // ✅ 可以访问 radius
} else {
return shape.width * shape.height; // ✅ 可以访问 width、height
}
}
📌 关键: 每个接口都有
kind 属性,但值是不同的字面量类型。TypeScript 通过 kind 的值就能收窄类型,安全访问对应接口的专属属性。
(2) 模式二:可选值 + undefined
TYPESCRIPT
type Result = string | undefined;
function search(query: string): Result {
if (query.length === 0) return undefined;
return "找到:" + query;
}
let result = search("TypeScript");
if (result !== undefined) {
console.log(result.toUpperCase()); // ✅ 收窄为 string
}
(3) 模式三:状态机
TYPESCRIPT
type RequestStatus = "idle" | "loading" | "success" | "error";
interface RequestState {
status: RequestStatus;
data?: string;
error?: string;
}
function renderState(state: RequestState): string {
switch (state.status) {
case "idle":
return "等待中...";
case "loading":
return "加载中...";
case "success":
return "成功:" + state.data; // ✅ success 状态下 data 存在
case "error":
return "错误:" + state.error; // ✅ error 状态下 error 存在
}
}
5. never 类型与穷尽检查
当 TypeScript 收窄到"不可能的情况"时,类型会变成 never:
TYPESCRIPT
type Shape = "circle" | "square";
function getIcon(shape: Shape): string {
switch (shape) {
case "circle": return "⭕";
case "square": return "⬜";
default:
// 如果所有 case 都处理了,shape 在这里类型是 never
const _exhaustiveCheck: never = shape; // ✅ 如果漏掉 case,这里会报错
return _exhaustiveCheck;
}
}
💡 技巧:
never 的穷尽检查模式——在 switch 的 default 分支里把值赋给 never 类型变量。如果未来给 Shape 新增了成员但忘记添加 case,TypeScript 会在 default 处报错,提醒你补全。
▶ 示例:穷尽检查的实际效果
TYPESCRIPT
type TrafficLight = "red" | "yellow" | "green";
function getAction(light: TrafficLight): string {
switch (light) {
case "red": return "停止";
case "yellow": return "注意";
// 故意遗漏 "green" 的 case
default:
// 如果加上下一行,TypeScript 会报错:
// 不能将类型 "green" 赋值给类型 "never"
const exhaustive: never = light;
return "未知";
}
}
❓ 常见问题
Q 联合类型和交叉类型有什么区别?
A 联合类型(A | B)表示"或者是A,或者是B"——取并集,值只需要满足其中一个。交叉类型(A & B)表示"既是A,又是B"——取交集,值必须同时满足两者。第20课会详解交叉类型。
Q 字面量类型和枚举(enum)该用哪个?
A 优先用字面量联合类型。它比枚举更轻量(编译后没有额外代码),且和 JavaScript 原生字符串/数字完全兼容。枚举适合需要反向映射(值→名)或运行时需要遍历所有成员的场景。第12课会对比两者。
Q typeof 收窄能判断所有类型吗?
A 不能。typeof 只能判断 "number"、"string"、"boolean"、"symbol"、"bigint"、"object"、"function"、"undefined" 这几种。对于 null(typeof null 是 "object")、数组、Date 等引用类型,需要用 instanceof 或其他方式收窄。第17课详解。
Q never 类型什么时候会用到?
A never 主要在两个场景出现:(1) 类型收窄到不可能的分支时自动出现 (2) 函数永远不会有返回值时(如抛出异常或无限循环)。初学者只需要理解"穷尽检查"模式即可——在 switch 的 default 里用 never 检查是否遗漏 case。
📖 小节
- 联合类型用
|连接多种类型,表示值可以是其中任意一种 - 字面量类型限制值为特定的字面量(字符串、数字、布尔),配合联合类型实现枚举效果
- 联合类型变量只能访问所有成员共有的属性,需要通过 typeof/in/instanceof 等方式"收窄"后才能访问特定成员
- 可辨识联合(Discriminated Union)是联合+字面量的经典模式——用共享属性的值区分类型
- never 类型用于穷尽检查,确保 switch 语句覆盖所有可能
📝 作业
- 基础题(难度⭐):定义一个联合类型
Status = "active" | "inactive" | "banned",写一个函数getStatusText(status: Status)返回对应的中文描述。 - 进阶题(难度⭐⭐):定义一个可辨识联合类型
Result = SuccessResult | ErrorResult,其中SuccessResult有success: true和data: string,ErrorResult有success: false和error: string。写一个函数处理 Result,根据success的值安全访问data或error。 - 挑战题(难度⭐⭐⭐):用穷尽检查模式写一个函数
describeDay(day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun"),在 switch 中处理所有7天。然后故意漏掉一天,观察 never 穷尽检查的报错效果。