跳到主要内容

TypeScript 类型体操与工程实践

🧩 类型体操的目标不是追求复杂,而是用可维护的类型约束表达真实业务规则,并把错误尽可能提前到编译阶段。

类型系统基础

  • 联合类型 A | B:值满足其中一种类型。
  • 交叉类型 A & B:同时具备多种类型的结构。
  • 泛型:让类型参数随调用方传入,保留输入和输出之间的关系。
  • 类型收窄:通过 typeofin、判别字段或自定义守卫缩小联合类型。
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: Error }

function unwrap<T>(result: Result<T>): T {
if (result.ok) return result.data
throw result.error
}

常用类型工具

type User = {
id: string
name: string
email?: string
}

type UserPatch = Partial<Omit<User, 'id'>>
type PublicUser = Pick<User, 'id' | 'name'>
type UserMap = Record<string, User>

内置工具的核心通常来自映射类型、条件类型与索引访问类型。

条件类型与 infer

type AwaitedValue<T> = T extends Promise<infer U> ? AwaitedValue<U> : T
type FnReturn<T> = T extends (...args: any[]) => infer R ? R : never

条件类型遇到裸类型参数的联合类型时会分发:

type ToArray<T> = T extends unknown ? T[] : never
type Value = ToArray<string | number> // string[] | number[]

若要阻止分发,可使用元组包装:[T] extends [U]

映射类型与键重映射

type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

type ReadonlyDeep<T> = T extends (...args: any[]) => any
? T
: T extends object
? { readonly [K in keyof T]: ReadonlyDeep<T[K]> }
: T

模板字面量类型

适合表达事件名、路由参数和样式 token:

type EventName<T extends string> = `${T}Changed`
type CSSSize = `${number}px` | `${number}rem` | 0

工程实践

1. 边界处验证运行时数据

类型只存在于编译期。接口响应、URL 参数、LocalStorage 等外部输入必须使用 Zod、Valibot 或手写守卫校验。

import { z } from 'zod'
const UserSchema = z.object({ id: z.string(), name: z.string() })
type User = z.infer<typeof UserSchema>
const user = UserSchema.parse(input)

2. 优先使用 unknown

any 会关闭类型检查;unknown 要求先收窄再使用。

3. 使用 satisfies 保留推导

const routes = {
home: '/',
profile: '/users/:id',
} satisfies Record<string, string>

4. 用判别联合建模状态

避免 loading?: booleandata?: Terror?: Error 产生非法组合。

5. 控制类型复杂度

  • 为复杂工具类型写测试和注释。
  • 避免无限递归和过深实例化。
  • 公共 API 显式声明返回类型。
  • 业务类型优先清晰,不为减少几行代码而牺牲可读性。

tsconfig 建议

  • 开启 strict
  • 开启 noUncheckedIndexedAccess
  • 开启 exactOptionalPropertyTypes
  • 库项目生成声明文件并谨慎维护导出边界

自检清单

  • 能使用泛型保留输入输出关系
  • 能解释条件类型分发
  • 能用判别联合消除非法状态
  • 能区分编译期类型与运行时校验
  • 能读懂常见映射类型和 infer