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。

📖 小节

📝 作业

  1. 基础题(难度⭐):定义一个联合类型 Status = "active" | "inactive" | "banned",写一个函数 getStatusText(status: Status) 返回对应的中文描述。
  2. 进阶题(难度⭐⭐):定义一个可辨识联合类型 Result = SuccessResult | ErrorResult,其中 SuccessResultsuccess: truedata: stringErrorResultsuccess: falseerror: string。写一个函数处理 Result,根据 success 的值安全访问 dataerror
  3. 挑战题(难度⭐⭐⭐):用穷尽检查模式写一个函数 describeDay(day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun"),在 switch 中处理所有7天。然后故意漏掉一天,观察 never 穷尽检查的报错效果。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏