高级类型:联合、交叉与映射类型
2026/7/9大约 6 分钟
高级类型:联合、交叉与映射类型
概述
TypeScript 的类型系统之所以强大,很大程度上归功于其丰富的类型操作能力:联合类型(Union)、交叉类型(Intersection)、映射类型(Mapped Types)、条件类型(Conditional Types)、模板字面量类型(Template Literal Types)以及 satisfies 操作符。对于 Go 开发者来说,这些概念大部分是全新的 —— Go 的类型系统远没有如此丰富的表达能力。
Go 开发者已知
// Go 的类型操作非常有限
type Number interface {
~int | ~float64 // Go 1.18+ 联合约束
}
// 接口组合(类似交叉)
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface {
Reader
Writer
}
// 类型别名
type ID = string
type Handler func(w http.ResponseWriter, r *http.Request)Go 的类型系统限制:
- 不支持联合类型(
interface约束除外) - 不支持交叉类型(
&) - 不支持映射类型
- 不支持条件类型
- 不支持模板字面量类型
- 不支持
keyof/typeof操作符
TypeScript 怎么做
联合类型深度用法
// 基本联合
type ID = string | number
// 联合中的字面量类型
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
// 联合中的对象类型(可辨识联合)
type Result<T> =
| { status: 'success'; data: T }
| { status: 'error'; message: string }
| { status: 'loading' }
// 联合类型的分发(Distributive Conditional Types)
type ToArray<T> = T extends unknown ? T[] : never
type StrOrNum = ToArray<string | number>
// string[] | number[](分发给联合的每个成员)
// 从联合中排除类型
type WithoutString = Exclude<string | number | boolean, string>
// number | boolean交叉类型深度用法
// 基本交叉
type Named = { name: string }
type Aged = { age: number }
type Person = Named & Aged // { name: string; age: number }
// 交叉合并同名属性
type A = { id: number; name: string }
type B = { id: string; email: string }
type AB = A & B
// 最终 id 类型为 never(number & string 不可共存)
// 交叉类型用于 mixin
type Disposable = { dispose: () => void }
type Activatable = { activate: () => void; deactivate: () => void }
type Widget = Disposable & Activatable & { id: string }映射类型
// 基础映射
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
type MyPartial<T> = {
[K in keyof T]?: T[K]
}
type MyRequired<T> = {
[K in keyof T]-?: T[K] // -? 去除可选
}
// 键名重映射(TS 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
interface User { name: string; age: number }
type UserGetters = Getters<User>
// { getName: () => string; getAge: () => number }
// 根据值过滤键
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never
}[keyof T]
interface Obj { a: string; b: number; c: string }
type S = StringKeys<Obj> // 'a' | 'c'内置工具类型
interface User {
name: string
age: number
email: string
password: string
}
// Pick —— 选取属性
type PublicUser = Pick<User, 'name' | 'email'>
// Omit —— 排除属性
type SafeUser = Omit<User, 'password'>
// Record —— 键值对
type HttpHeaders = Record<string, string>
type StatusMap = Record<number, string>
// Exclude —— 联合排除
type T = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// Extract —— 联合提取
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'c'> // 'a' | 'c'
// NonNullable —— 排除 null/undefined
type T3 = NonNullable<string | number | null | undefined>
// string | number
// ReturnType —— 函数返回类型
function fetchUser(): Promise<User>
type FetchResult = ReturnType<typeof fetchUser>
// Promise<User>
// Parameters —— 函数参数元组
type FetchParams = Parameters<typeof fetchUser>
// []keyof / typeof 操作符
// keyof —— 获取对象类型的键联合
interface Person {
name: string
age: number
email: string
}
type PersonKeys = keyof Person // 'name' | 'age' | 'email'
// typeof —— 获取值的类型
const config = { host: 'localhost', port: 8080 }
type ConfigType = typeof config
// { host: string; port: number }
// 组合使用
const HTTP_METHODS = { GET: 'GET', POST: 'POST' } as const
type MethodKey = keyof typeof HTTP_METHODS // 'GET' | 'POST'
type MethodValue = (typeof HTTP_METHODS)[keyof typeof HTTP_METHODS]
// 'GET' | 'POST'模板字面量类型
// 基础模板字面量
type EventName = `on${Capitalize<string>}`
// on + 任何大写开头的字符串
// 联合类型模板
type Size = 'small' | 'medium' | 'large'
type Color = 'red' | 'green' | 'blue'
type ButtonStyle = `${Size}-${Color}`
// 'small-red' | 'small-green' | ... | 'large-blue'
// 实用工具
type HttpHeader = `x-${string}`
// 所有以 x- 开头的字符串
// 字符串操作类型
type Upper = Uppercase<'hello'> // 'HELLO'
type Lower = Lowercase<'HELLO'> // 'hello'
type Cap = Capitalize<'hello'> // 'Hello'
type Uncap = Uncapitalize<'Hello'> // 'hello'satisfies 操作符
// satisfies —— 检查类型但不改变推断结果
type Palette = Record<string, string | { hex: string; rgb: string }>
const palette = {
red: '#FF0000',
green: { hex: '#00FF00', rgb: '0,255,0' },
blue: '#0000FF',
} satisfies Palette
// palette.red 的类型是 string(而不是 string | { hex: string; rgb: string })
// 但类型检查确保 palette 满足 Palette 约束
// 实际用途:精确类型推断 + 类型安全
const routes = {
home: '/',
users: '/users/:id',
settings: '/settings',
} satisfies Record<string, `/${string}`>
// routes.home 类型是 '/',而不是 string差异分析
| 维度 | Go | TypeScript |
|---|---|---|
| 联合类型 | 仅限泛型约束 interface { ~int } | 一等公民 string | number |
| 交叉类型 | 接口嵌入 | A & B |
| 映射类型 | 不支持 | [K in keyof T]: T[K] |
| 条件类型 | 不支持 | T extends U ? X : Y |
| 模板字面量 | 不支持 | `prefix-${string}` |
| 类型操作符 | 无 | keyof / typeof |
| satisfies | 无 | TS 4.9+ |
Bad Practice
// Bad: 嵌套过深难以阅读
type DeepNested<T> = T extends object
? { [K in keyof T]: DeepNested<T[K]> }
extends infer U
? U extends { [K: string]: unknown }
? { readonly [K in keyof U]: U[K] }
: never
: never
: T
// Bad: 过度使用条件类型
type WhatIsThis<T> =
T extends string
? T extends `${infer _}${infer _}`
? 'at least 2 chars'
: '1 char'
: 'not string'
// Bad: 无意义的映射覆盖
interface User { name: string; age: number }
type BadMap = {
[K in keyof User]: string // 丢失了原始类型信息
}
// 得到 { name: string; age: string }
// Bad: 联合类型中使用 any
type BadUnion = any | string // 结果是 anyBest Practice
// Good: 注释复杂类型
/**
* 递归地将所有属性变为只读
* @example
* type ReadonlyUser = DeepReadonly<User>
*/
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K]
}
// Good: 内置工具类型组合
interface APIResponse {
data: unknown
status: number
message: string
}
// 类型安全的 API 处理
type ApiResult<T> = Promise<{
data: T
status: number
message: string
}>
// Good: 映射类型保持意图清晰
type Nullable<T> = {
[K in keyof T]: T[K] | null
}
type Serializable<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K]
}
// Good: 模板字面量用于事件类型
type AppEvent = `app:${string}`
type UserEvent = `user:${'created' | 'updated' | 'deleted'}`
type AnalyticsEvent = `analytics:${'pageview' | 'click' | 'scroll'}`
type AllEvents = AppEvent | UserEvent | AnalyticsEvent
// Good: 使用 satisfies 保持精确类型
const buttonSize = {
sm: { padding: '4px', fontSize: '12px' },
md: { padding: '8px', fontSize: '14px' },
lg: { padding: '12px', fontSize: '16px' },
} satisfies Record<string, { padding: string; fontSize: string }>
// Good: 给复杂类型加别名
type UserKeys = keyof User
type UserWithoutPassword = Omit<User, 'password'>
type PartialUser = Partial<UserWithoutPassword>类型编程复杂度准则
- 能用 interface 解决的,不用 type 体操
- 复杂类型必须加注释
- 每个类型操作不超过 3 层嵌套
- 优先使用内置工具类型,而非手写
- 类型编程是手段,不是目的
// 优先使用内置类型
type ExcludeNull<T> = NonNullable<T> // 而非手写
type PickName = Pick<User, 'name'> // 而非映射内置工具类型速查表
| 工具类型 | 作用 | 定义 |
|---|---|---|
Partial<T> | 全部可选 | { [K in keyof T]?: T[K] } |
Required<T> | 全部必填 | { [K in keyof T]-?: T[K] } |
Readonly<T> | 全部只读 | { readonly [K in keyof T]: T[K] } |
Pick<T, K> | 选取键 | 映射 + 约束 |
Omit<T, K> | 排除键 | Pick<T, Exclude<keyof T, K>> |
Record<K, V> | 键值对象 | { [P in K]: V } |
Exclude<T, U> | 联合排除 | T extends U ? never : T |
Extract<T, U> | 联合提取 | T extends U ? T : never |
NonNullable<T> | 去 null/undefined | T extends null | undefined ? never : T |
ReturnType<T> | 函数返回值 | T extends (...args: any[]) => infer R ? R : any |
Parameters<T> | 函数参数元组 | T extends (...args: infer P) => any ? P : never |
Awaited<T> | Promise 值 | 递归 then 链 |
总结
| Go 概念 | TypeScript 对应 |
|---|---|
| 接口组合 | 交叉类型 A & B |
interface 约束联合 | 联合类型 A | B |
| 无对应 | 映射类型 [K in keyof T] |
| 无对应 | 条件类型 T extends U ? X : Y |
| 无对应 | 模板字面量 ` ` |
| 无对应 | satisfies 操作符 |
| 无对应 | 内置工具类型 |
TypeScript 的类型编程能力是其最强大的特性之一,但也应理性使用。遵循 写给人读的代码 原则,让类型系统为你服务,而非被它奴役。
下一章将介绍 模块系统与命名空间。