首页 / TypeScript 入门教程 / 联合类型与交叉类型

TypeScript 入门教程

联合类型与交叉类型

本教程共 80 篇 · 第 15 篇 · 更新于 2026-08-10 · 约 10 分钟阅读

TypeScriptTypeScript 入门教程联合类型交叉类型UnionIntersection

本节目标:学会用 | 组合多种可能性(联合类型),用 & 组合多个类型的特性(交叉类型),理解联合类型的”共有成员”限制和类型收窄,掌握交叉类型在对象组合中的正确用法。

联合类型:A 或 B

联合类型用 | 竖线连接多个类型,表示”这个值可以是 A,也可以是 B”。

// id 可以是 string,也可以是 number
let id: string | number;
id = "abc123";  // ✅
id = 456;       // ✅
// id = true;   // ❌ boolean 不在联合中

函数的参数经常用联合类型来增加灵活性:

function printId(id: string | number) {
  console.log(`ID: ${id}`);
}

printId("user_001");  // ✅
printId(42);          // ✅
// printId({ id: 1 });  // ❌ 对象不在联合中

联合类型的”陷阱”:只能访问共有成员

拿到一个联合类型的值后,你只能访问每个成员类型都有的属性和方法。这听起来反直觉——毕竟是”并集”,怎么变成”交集”了?

function getLength(value: string | number) {
  // value.length;  // ❌ number 没有 length 属性
  // 如果 value 是 number,访问 length 会出问题
  return String(value).length;  // ✅ 确保是 string 再操作
}

这个行为其实很合理:TypeScript 不知道运行时这个值到底是 string 还是 number,所以只允许调用两方都有的操作(比如 toString()valueOf())。这也叫”进门前先检查所有可能”。

换句话说,联合类型的是并集(可以是任何一个),但可用的操作是交集(只有共有的才能用)。

类型收窄:让联合类型恢复”具体类型”

要想使用某个成员独有的方法,必须先收窄类型——用代码判断出它到底是哪一种:

function processId(id: string | number) {
  if (typeof id === "string") {
    // 这个分支里,id 被收窄为 string
    console.log(id.toUpperCase());
  } else {
    // 这个分支里,id 被收窄为 number
    console.log(id.toFixed(2));
  }
}

typeof 是最常用的收窄方式。针对对象的联合类型,你可以用 in 操作符或字面量字段区分:

type Success = { status: "ok"; data: string };
type Error = { status: "error"; message: string };
type Response = Success | Error;

function handleResponse(res: Response) {
  if (res.status === "ok") {
    // 这里 res 被收窄为 Success
    console.log("数据:", res.data);
  } else {
    // 这里 res 被收窄为 Error
    console.log("错误:", res.message);
  }
}

这种模式有个专门的名字——可辨识联合(discriminated union)。每个成员有一个共享的”辨识字段”(这里的 status),TypeScript 通过这个字段来判断具体类型。

联合类型作为”选项集”

联合类型最常见的用途之一是把变量限制在一组固定值中:

type Direction = "up" | "down" | "left" | "right";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Alignment = "left" | "center" | "right";

function align(text: string, direction: Alignment) {
  console.log(`${text} 对齐到 ${direction}`);
}

align("标题", "center");  // ✅
// align("标题", "middle");  // ❌ "middle" 不在 Alignment 中

配合编辑器的自动补全,这种写法的开发体验非常好——输到一半就能看到所有可选值。

交叉类型:A 且 B

交叉类型用 & 连接多个类型,表示”这个值同时满足 A 和 B”。

type Person = {
  name: string;
  age: number;
};

type Worker = {
  company: string;
  role: string;
};

// Employee 同时有 Person 和 Worker 的所有属性
type Employee = Person & Worker;

let alice: Employee = {
  name: "Alice",
  age: 28,
  company: "Google",
  role: "工程师"
};

交叉类型在组合对象类型时最有用——你有一个”基础属性”类型和一个”扩展属性”类型,用 & 一交叉就得到了完整类型。

跟接口继承有什么不同

接口可以用 extends 继承,交叉类型用 & 组合,效果类似:

// 接口继承
interface Employee2 extends Person, Worker { }

// 交叉类型
type Employee3 = Person & Worker;

主要区别:接口支持声明合并(同样的接口名多次声明会自动合并),交叉类型不支持。对初学者来说,对象组合时用哪种差别不大,挑顺手的。

交叉类型的冲突处理

当两个类型的同名属性类型不兼容时,交叉会得到 never

type A = { value: string };
type B = { value: number };

type C = A & B;
// C 的类型是 { value: never }
// 因为 value 不可能同时是 string 和 number,所以被推断为 never

let obj: C = {
  // value: ???   ❌ 无法赋值——没有任何值同时是 string 和 number
};

如果同名属性的类型兼容(比如一个比另一个更具体),交叉会取交集:

type A = { value: string | number };
type B = { value: number };

type C = A & B;
// C 的类型是 { value: number }——取其交集

number 既是 string | number 的子集,也是 number 本身,所以交集是 number

联合与交叉的混合使用

两种类型可以组合使用。先看一个实用例子——函数重载的替代方案:

// 联合作参数,交叉作返回值
type Config = { host: string; port: number };
type Overrides = { host?: string; port?: number; debug?: boolean };

function mergeConfig(base: Config, overrides: Overrides): Config & Partial<Overrides> {
  return { ...base, ...overrides };
}

let result = mergeConfig(
  { host: "localhost", port: 3000 },
  { port: 8080, debug: true }
);
// result 的类型包含 host, port, debug

再来看一个分配律的规则——交叉对联合有分配律

type T = (A | B) & C;
// 等价于 (A & C) | (B & C)

知道这个有助于理解复杂类型的推导结果。

可运行示例

// 联合类型与交叉类型综合示例

// === 联合类型 ===

// 1. 基本联合
function formatInput(input: string | number): string {
  if (typeof input === "string") {
    return `"${input}"`;        // 收窄为 string
  }
  return input.toFixed(2);      // 收窄为 number
}

console.log(formatInput("hello"));  // "hello"
console.log(formatInput(3.14159));  // "3.14"

// 2. 可辨识联合
type ApiResult =
  | { status: "success"; data: string[] }
  | { status: "error"; code: number; message: string };

function handleApiResult(result: ApiResult) {
  switch (result.status) {
    case "success":
      console.log(`成功!数据: ${result.data.join(", ")}`);
      break;
    case "error":
      console.log(`失败 [${result.code}]: ${result.message}`);
      break;
  }
}

handleApiResult({ status: "success", data: ["A", "B", "C"] });
handleApiResult({ status: "error", code: 404, message: "未找到" });

// === 交叉类型 ===

// 3. 对象组合
type Timestamp = { createdAt: Date; updatedAt: Date };
type AuthorInfo = { author: string; version: number };

type Document = Timestamp & AuthorInfo & { title: string };

let doc: Document = {
  title: "TypeScript 入门",
  author: "码上学",
  version: 1,
  createdAt: new Date("2026-08-10"),
  updatedAt: new Date("2026-08-10")
};

console.log(`文档: ${doc.title} by ${doc.author} v${doc.version}`);

// 4. 联合字面量作选项(最常见的"联合用法")
type Theme = "light" | "dark";
type Size = "sm" | "md" | "lg";

function renderButton(text: string, theme: Theme, size: Size) {
  console.log(`[${theme}/${size}] ${text}`);
}

renderButton("确定", "dark", "lg");
// renderButton("确定", "blue", "lg");  // ❌ 编译错误

输出:

"hello"
3.14
成功!数据: A, B, C
失败 [404]: 未找到
文档: TypeScript 入门 by 码上学 v1
[dark/lg] 确定

小结

联合类型 A | B 扩大取值范围,交叉类型 A & B 合并类型特性。联合类型的关键是”先收窄再使用”,交叉类型的关键是”所有属性都得有”。联合字面量是最简单也最常用的类型约束工具之一。下一章是基础类型的最后一站——字面量类型,你会看到 as const 怎么把普通值变成精确的类型约束。