TypeScript 类型体操进阶:从入门到精通
为什么 2026 年必须掌握高级 TypeScript
2026 年,TypeScript 已经是前端和 Node.js 项目的标配。但大多数开发者只停留在「给变量加类型」的阶段,真正的类型编程能力——泛型约束、条件类型、infer 推导、映射类型——才是区分初级和高级 TypeScript 开发者的分水岭。掌握类型体操,你可以在编译期捕获更多错误,减少运行时 Bug,同时让代码的自文档化能力大幅提升。
类型体操能解决什么问题
- 编译期校验:将运行时错误提前到编译期,如 API 响应字段缺失、表单校验规则不一致
- 代码提示增强:精准的智能提示让开发效率翻倍,减少查阅文档的频率
- 重构安全网:类型系统是重构的最强保障,改一个字段类型,所有引用处立即报错
- 领域建模:用 branded types、discriminated unions 在类型层面表达业务规则
- 零运行时开销:所有类型体操在编译后全部擦除,不影响包体积和性能
从简单类型到类型编程的演进
// 初级:给变量加类型
const username: string = '张三'
const age: number = 25
// 中级:使用泛型和接口
interface ApiResponse<T> {
code: number
data: T
message: string
}
// 高级:类型编程 - 在类型层面做计算
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K]
}
type User = {
name: string
profile: { avatar: string; bio: string }
}
type FrozenUser = DeepReadonly<User>
// { readonly name: string; readonly profile: { readonly avatar: string; readonly bio: string } }
💡 使用 JSON 转 TypeScript 工具 快速从 API 响应生成类型定义,然后在此基础上做高级类型编程。
泛型约束与默认值
extends 约束泛型范围
泛型约束让你限制类型参数的范围,避免传入不合法的类型。
// 约束 T 必须有 id 属性
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id)
}
interface User { id: number; name: string }
interface Product { id: number; title: string; price: number }
const users: User[] = [{ id: 1, name: '张三' }]
const products: Product[] = [{ id: 1, title: 'TypeScript 书', price: 99 }]
findById(users, 1) // ✅ User | undefined
findById(products, 1) // ✅ Product | undefined
findById([1, 2, 3], 1) // ❌ number 没有 id 属性
keyof 约束属性名
// 约束 K 必须是 T 的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user = { name: '张三', age: 25, email: 'zhangsan@test.com' }
getProperty(user, 'name') // ✅ string
getProperty(user, 'age') // ✅ number
getProperty(user, 'phone') // ❌ 'phone' 不是 user 的属性
泛型默认值
interface PaginatedResponse<T, Meta = { total: number; page: number }> {
data: T[]
meta: Meta
}
type UserList = PaginatedResponse<User>
// meta 默认为 { total: number; page: number }
type CustomMeta = { total: number; page: number; hasNext: boolean }
type UserListCustom = PaginatedResponse<User, CustomMeta>
// meta 使用自定义类型
多泛型约束组合
// T 必须同时满足两个约束
type RequireBoth<T extends A & B, A, B> = T
interface HasId { id: number }
interface HasName { name: string }
function mergeEntities<T extends HasId & HasName>(a: T, b: T): T[] {
return [a, b]
}
mergeEntities({ id: 1, name: '张三' }, { id: 2, name: '李四' }) // ✅
mergeEntities({ id: 1 }, { id: 2 }) // ❌ 缺少 name
条件类型:extends ? :
基本语法
条件类型就像类型层面的三元表达式:T extends U ? X : Y
type IsString<T> = T extends string ? 'yes' : 'no'
type A = IsString<string> // 'yes'
type B = IsString<number> // 'no'
type C = IsString<'hello'> // 'yes'(字面量类型也匹配 string)
分布式条件类型
当 T 是联合类型时,条件类型会自动分发:
type ToArray<T> = T extends unknown ? T[] : never
type Result = ToArray<string | number>
// 分发过程:ToArray<string> | ToArray<number>
// 结果:string[] | number[]
// 注意:这不是 (string | number)[]
阻止分发
用 [T] 包裹来阻止分发行为:
type ToArrayNoDistribute<T> = [T] extends [unknown] ? T[] : never
type Result2 = ToArrayNoDistribute<string | number>
// 结果:(string | number)[],不会分发
实战:类型过滤
// 从联合类型中过滤掉某些类型
type Exclude<T, U> = T extends U ? never : T
type Extract<T, U> = T extends U ? T : never
type AllTypes = string | number | boolean | null | undefined
type NonNull = Exclude<AllTypes, null | undefined>
// string | number | boolean
type OnlyString = Extract<AllTypes, string>
// string
// 过滤对象类型中的某些属性
type PickByValue<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K]
}
interface FormState {
username: string
age: number
email: string
isActive: boolean
}
type StringFields = PickByValue<FormState, string>
// { username: string; email: string }
infer 关键字深度解析
infer 基础:在条件类型中提取类型
infer 只能在 extends 条件类型中使用,用于声明一个待推断的类型变量。
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
function getUser() {
return { name: '张三', age: 25 }
}
type UserType = ReturnType<typeof getUser>
// { name: string; age: number }
// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never
function greet(name: string, age: number): string {
return `Hello, ${name}, age ${age}`
}
type GreetParams = Parameters<typeof greet>
// [string, number]
提取 Promise 内部类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
type P1 = Awaited<Promise<string>> // string
type P2 = Awaited<Promise<Promise<number>>> // number(递归解包)
type P3 = Awaited<string> // string(非 Promise 直接返回)
提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type Items = ElementOf<string[]> // string
type Nums = ElementOf<number[]> // number
type Mixed = ElementOf<(string | number)[]> // string | number
// 提取元组第一个元素
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never
type First = Head<[string, number, boolean]> // string
// 提取元组最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never
type Tail = Last<[string, number, boolean]> // boolean
多 infer 协同推断
// 提取类构造函数的实例类型
type InstanceOf<T> = T extends new (...args: any[]) => infer I ? I : never
class UserService {
constructor(private id: number) {}
getUser() { return { id: this.id } }
}
type ServiceInstance = InstanceOf<typeof UserService>
// UserService
// 同时提取请求和响应类型
type ApiTypes<T> = T extends (req: infer Req) => Promise<infer Res>
? { request: Req; response: Res }
: never
async function updateUser(req: { id: number; name: string }): Promise<{ success: boolean }> {
return { success: true }
}
type UpdateUserTypes = ApiTypes<typeof updateUser>
// { request: { id: number; name: string }; response: { success: boolean } }
映射类型:从零实现内置工具类型
Record 实现
// 内置 Record<K, V> 的实现原理
type MyRecord<K extends keyof any, V> = {
[P in K]: V
}
type UserRoles = MyRecord<'admin' | 'editor' | 'viewer', boolean>
// { admin: boolean; editor: boolean; viewer: boolean }
// 实战:创建枚举映射
type StatusMap = MyRecord<'pending' | 'active' | 'closed', { label: string; color: string }>
const statusConfig: StatusMap = {
pending: { label: '待处理', color: '#f59e0b' },
active: { label: '进行中', color: '#10b981' },
closed: { label: '已关闭', color: '#6b7280' },
}
Partial 和 Required 实现
// Partial:所有属性变为可选
type MyPartial<T> = {
[K in keyof T]?: T[K]
}
// Required:所有属性变为必选
type MyRequired<T> = {
[K in keyof T]-?: T[K]
}
// 深层 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? T[K] extends Function
? T[K]
: DeepPartial<T[K]>
: T[K]
}
interface Config {
db: { host: string; port: number }
cache: { ttl: number; max: number }
}
type PartialConfig = DeepPartial<Config>
// { db?: { host?: string; port?: number }; cache?: { ttl?: number; max?: number } }
Pick 和 Omit 实现
// Pick:选取部分属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
// Omit:排除部分属性
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
interface FullUser {
id: number
name: string
email: string
password: string
createdAt: string
}
// 安全的用户信息,排除密码
type SafeUser = MyOmit<FullUser, 'password'>
// { id: number; name: string; email: string; createdAt: string }
// 只更新部分字段
type UserUpdate = MyPartial<MyPick<FullUser, 'name' | 'email'>>
// { name?: string; email?: string }
键重映射(Key Remapping)
TypeScript 4.1+ 支持用 as 对键进行重映射:
// 属性名加前缀
type AddPrefix<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K]
}
interface ApiData { name: string; age: number }
type PrefixedData = AddPrefix<ApiData, 'user'>
// { userName: string; userAge: number }
// 将所有属性变为 getter
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
type UserGetters = Getters<{ name: string; age: number }>
// { getName: () => string; getAge: () => number }
模板字面量类型
基础用法
type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'
// CSS 属性类型
type CSSUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw'
type CSSValue = `${number}${CSSUnit}`
const width: CSSValue = '100px' // ✅
const height: CSSValue = '50vh' // ✅
const invalid: CSSValue = 'abc' // ❌ 不匹配模板
结合映射类型实现类型安全的事件系统
type Events = {
click: { x: number; y: number }
keydown: { key: string; code: string }
resize: { width: number; height: number }
}
type EventHandlerMap = {
[K in keyof Events as `on${Capitalize<string & K>}`]: (payload: Events[K]) => void
}
type Listeners = EventHandlerMap
// {
// onClick: (payload: { x: number; y: number }) => void
// onKeyDown: (payload: { key: string; code: string }) => void
// onResize: (payload: { width: number; height: number }) => void
// }
const listeners: Listeners = {
onClick: (e) => console.log(e.x, e.y),
onKeyDown: (e) => console.log(e.key),
onResize: (e) => console.log(e.width),
}
字符串解析类型
// 解析路由参数
type ParseRouteParams<S extends string> =
S extends `${string}:${infer Param}/${infer Rest}`
? Param | ParseRouteParams<Rest>
: S extends `${string}:${infer Param}`
? Param
: never
type Params = ParseRouteParams<'/user/:id/post/:postId'>
// 'id' | 'postId'
// 解析版本号
type ParseVersion<V extends string> =
V extends `${infer Major}.${infer Minor}.${infer Patch}`
? { major: Major; minor: Minor; patch: Patch }
: never
type V = ParseVersion<'1.2.3'>
// { major: '1'; minor: '2'; patch: '3' }
递归类型
深层 Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K]
}
const config: DeepReadonly<{
db: { host: string; port: number }
cache: { ttl: number }
}> = {
db: { host: 'localhost', port: 3306 },
cache: { ttl: 3600 },
}
// config.db.host = '127.0.0.1' // ❌ 只读
深层 Partial 与合并
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T
type DeepMerge<A, B> = {
[K in keyof A | keyof B]: K extends keyof B
? B[K] extends object
? K extends keyof A
? A[K] extends object
? DeepMerge<A[K], B[K]>
: B[K]
: B[K]
: B[K]
: K extends keyof A
? A[K]
: never
}
递归元组操作
// 元组反转
type Reverse<T extends any[]> = T extends [infer H, ...infer R]
? [...Reverse<R>, H]
: []
type Reversed = Reverse<[1, 2, 3, 4]> // [4, 3, 2, 1]
// 元组扁平化
type Flatten<T extends any[]> = T extends [infer H, ...infer R]
? H extends any[]
? [...Flatten<H>, ...Flatten<R>]
: [H, ...Flatten<R>]
: []
type Flat = Flatten<[1, [2, 3], [4, [5, 6]]]> // [1, 2, 3, 4, 5, 6]
// 字符串长度
type StrLen<S extends string, Acc extends any[] = []> =
S extends `${string}${infer Rest}`
? StrLen<Rest, [...Acc, 0]>
: Acc['length']
type Len = StrLen<'hello'> // 5
类型级编程:算术与字符串操作
类型级加法
// 利用元组长度实现加法
type BuildTuple<N extends number, T extends any[] = []> =
T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>
type Add<A extends number, B extends number> =
[...BuildTuple<A>, ...BuildTuple<B>]['length']
type Sum = Add<3, 5> // 8
// 类型级减法
type Subtract<A extends number, B extends number> =
BuildTuple<A> extends [...BuildTuple<B>, ...infer Rest]
? Rest['length']
: never
type Diff = Subtract<10, 4> // 6
类型级字符串操作
// 字符串替换
type Replace<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer Before}${From}${infer After}`
? `${Before}${To}${After}`
: S
type Replaced = Replace<'hello world', 'world', 'TypeScript'>
// 'hello TypeScript'
// 驼峰转下划线
type CamelToSnake<S extends string> =
S extends `${infer H}${infer Rest}`
? H extends Uppercase<H>
? `_${Lowercase<H>}${CamelToSnake<Rest>}`
: `${H}${CamelToSnake<Rest>}`
: S
type Snake = CamelToSnake<'userName'> // 'user_name'
可辨识联合类型
基础模式
// 用 kind 字段做辨识
interface Circle { kind: 'circle'; radius: number }
interface Rectangle { kind: 'rectangle'; width: number; height: number }
interface Triangle { kind: 'triangle'; base: number; height: number }
type Shape = Circle | Rectangle | Triangle
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2
case 'rectangle': return shape.width * shape.height
case 'triangle': return 0.5 * shape.base * shape.height
}
}
状态机模式
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
function handleState<T>(state: RequestState<T>) {
switch (state.status) {
case 'idle': return '等待请求'
case 'loading': return '加载中...'
case 'success': return `数据: ${JSON.stringify(state.data)}`
case 'error': return `错误: ${state.error.message}`
}
}
// 状态转换是类型安全的
const idleState: RequestState<string> = { status: 'idle' }
// idleState.data // ❌ idle 状态没有 data
const successState: RequestState<string> = { status: 'success', data: 'hello' }
// successState.data // ✅ string
穷尽检查
// 利用 never 类型确保所有分支都被处理
function assertNever(x: never): never {
throw new Error(`未处理的类型: ${JSON.stringify(x)}`)
}
function processShape(shape: Shape) {
switch (shape.kind) {
case 'circle': return /* ... */ ''
case 'rectangle': return /* ... */ ''
case 'triangle': return /* ... */ ''
default: return assertNever(shape) // 如果漏掉某个 case,这里会报错
}
}
Branded Types:类型安全的利器
基础 Branded Type
// 用品牌类型区分相同结构但不同含义的类型
type Brand<T, B extends string> = T & { __brand: B }
type UserId = Brand<number, 'UserId'>
type OrderId = Brand<number, 'OrderId'>
function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }
const uid = 1 as UserId
const oid = 2 as OrderId
getUser(uid) // ✅
getUser(oid) // ❌ OrderId 不能赋给 UserId
getOrder(1) // ❅ number 不能赋给 UserId
类型安全的 ID 系统
function createId<T extends string>(brand: T) {
return (id: number) => id as Brand<number, T>
}
const userId = createId('UserId')
const orderId = createId('OrderId')
function deleteUser(id: UserId) { /* ... */ }
deleteUser(userId(1)) // ✅
deleteUser(orderId(1)) // ❌ 类型安全阻止了错误
类型安全的货币系统
type USD = Brand<number, 'USD'>
type CNY = Brand<number, 'CNY'>
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD
}
function addCNY(a: CNY, b: CNY): CNY {
return (a + b) as CNY
}
const price1 = 100 as USD
const price2 = 50 as USD
const cnyPrice = 200 as CNY
addUSD(price1, price2) // ✅
addUSD(price1, cnyPrice) // ❌ 不能把 CNY 和 USD 混加
satisfies 运算符
基础用法
satisfies 让你在不拓宽类型的情况下验证类型兼容性:
// ❌ 用类型注解会拓宽类型
const colors: Record<string, string | string[]> = {
primary: '#3b82f6',
secondary: '#6b7280',
gradients: ['#3b82f6', '#6b7280'],
}
// colors.primary.toUpperCase() // ❌ 类型是 string | string[],不能直接 toUpperCase
// ✅ 用 satisfies 保持字面量类型
const themeColors = {
primary: '#3b82f6',
secondary: '#6b7280',
gradients: ['#3b82f6', '#6b7280'],
} satisfies Record<string, string | string[]>
themeColors.primary.toUpperCase() // ✅ 类型是 '#3b82f6'
themeColors.gradients[0].toUpperCase() // ✅ 类型是 string
配置对象校验
interface FeatureConfig {
enabled: boolean
label: string
}
const features = {
darkMode: { enabled: true, label: '深色模式' },
notifications: { enabled: false, label: '通知' },
analytics: { enabled: true, label: '分析' },
} satisfies Record<string, FeatureConfig>
// features 的类型保留了所有键名,不会变成 Record<string, FeatureConfig>
features.darkMode.enabled // ✅ boolean
features.notifications.label // ✅ string
// features.nonExist.enabled // ❌ 属性不存在
const 类型参数
泛型推断字面量类型
TypeScript 5.0+ 的 const 类型参数让泛型推断保留字面量类型:
// ❌ 没有 const,T 被推断为 string
function createRoute<T>(path: T) {
return { path }
}
const route = createRoute('/user/:id')
// route.path 类型是 string,不是 '/user/:id'
// ✅ 使用 const 类型参数
function createRouteConst<const T>(path: T) {
return { path }
}
const routeConst = createRouteConst('/user/:id')
// routeConst.path 类型是 '/user/:id'
结合模板字面量类型
function defineEndpoints<const T extends Record<string, string>>(routes: T) {
type RouteKeys = keyof T
return routes as T & { _routeKeys: RouteKeys }
}
const api = defineEndpoints({
getUsers: '/api/users',
getUser: '/api/users/:id',
createUser: '/api/users',
})
// api 的键名被保留为字面量类型 'getUsers' | 'getUser' | 'createUser'
实战模式
API 响应类型安全
interface ApiRoutes {
'/api/users': {
response: { users: { id: number; name: string }[] }
}
'/api/users/:id': {
params: { id: number }
response: { id: number; name: string; email: string }
}
'/api/orders': {
query: { status?: string; page?: number }
response: { orders: { id: number; total: number }[]; total: number }
}
}
type ApiRoute = keyof ApiRoutes
async function apiCall<
Route extends ApiRoute,
Config extends ApiRoutes[Route]
>(
route: Route,
options: Omit<Config, 'response'> extends object ? Config : {}
): Promise<Config extends { response: infer R } ? R : never> {
const res = await fetch(route as string)
return res.json()
}
// 类型安全的 API 调用
const users = await apiCall('/api/users', {})
// users 类型: { users: { id: number; name: string }[] }
表单校验类型
type Validator<T> = (value: T) => string | null
type FormValidators<T> = {
[K in keyof T]?: Validator<T[K]> | Validator<T[K]>[]
}
interface LoginForm {
username: string
password: string
remember: boolean
}
const loginValidators: FormValidators<LoginForm> = {
username: (v) => v.length < 3 ? '用户名至少3个字符' : null,
password: (v) => v.length < 6 ? '密码至少6个字符' : null,
}
type ValidationError<T> = {
[K in keyof T]?: string[]
}
function validateForm<T>(
data: T,
validators: FormValidators<T>
): ValidationError<T> {
const errors: ValidationError<T> = {}
for (const key in validators) {
const validator = validators[key]
const value = data[key]
if (validator && value !== undefined) {
const result = Array.isArray(validator)
? validator.map(v => v(value)).filter(Boolean)
: validator(value)
if (result) errors[key] = Array.isArray(result) ? result : [result]
}
}
return errors
}
事件系统类型
interface EventMap {
'user:login': { userId: number; timestamp: number }
'user:logout': { userId: number }
'cart:add': { productId: number; quantity: number }
'cart:remove': { productId: number }
'order:create': { orderId: number; total: number }
}
type EventHandler<T extends keyof EventMap> = (payload: EventMap[T]) => void
class TypeSafeEventEmitter {
private handlers = new Map<string, Set<EventHandler<any>>>()
on<K extends keyof EventMap>(event: K, handler: EventHandler<K>) {
if (!this.handlers.has(event as string)) {
this.handlers.set(event as string, new Set())
}
this.handlers.get(event as string)!.add(handler)
return () => this.off(event, handler)
}
off<K extends keyof EventMap>(event: K, handler: EventHandler<K>) {
this.handlers.get(event as string)?.delete(handler)
}
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
this.handlers.get(event as string)?.forEach(h => h(payload))
}
}
const emitter = new TypeSafeEventEmitter()
emitter.on('user:login', (e) => console.log(e.userId)) // ✅
emitter.emit('cart:add', { productId: 1, quantity: 2 }) // ✅
// emitter.emit('cart:add', { wrong: true }) // ❌ 类型错误
常见类型错误及修复
错误 1:类型被意外拓宽
// ❌ 对象字面量类型被拓宽
const config = { port: 3000, host: 'localhost' }
// config.port 类型是 number,不是 3000
// ✅ 使用 as const
const configConst = { port: 3000, host: 'localhost' } as const
// configConst.port 类型是 3000
错误 2:泛型推断为联合类型
// ❌ T 被推断为 string | number,不是联合分发
function wrapInArray<T>(value: T): T[] {
return [value]
}
const result = wrapInArray(Math.random() > 0.5 ? 'hello' : 42)
// result 类型是 (string | number)[],不是 string[] | number[]
// ✅ 使用函数重载或条件类型
function wrapInArrayFixed<T>(value: T): [T] {
return [value]
}
错误 3:循环引用
// ❌ 类型循环引用
interface TreeNode {
value: string
children: TreeNode[] // ✅ 接口支持循环引用
}
// ❌ type 别名不支持循环引用
// type Node = { value: string; children: Node[] } // 可能报错
// ✅ 使用接口或添加泛型间接引用
type Node<T = Node> = { value: string; children: T[] }
错误 4:any 污染
// ❌ any 会污染所有接触它的类型
function parseJSON(str: string): any {
return JSON.parse(str)
}
const data = parseJSON('{"name":"张三"}')
data.nonExist.method() // 不报错,但运行时崩溃
// ✅ 使用 unknown
function parseJSONSafe(str: string): unknown {
return JSON.parse(str)
}
const safeData = parseJSONSafe('{"name":"张三"}')
// safeData.name // ❌ unknown 上不存在 name 属性
if (typeof safeData === 'object' && safeData !== null && 'name' in safeData) {
console.log((safeData as { name: string }).name) // ✅
}
类型调试技巧
查看中间类型
// 利用类型错误查看类型
type Debug<T> = { [K in keyof T]: T[K] }
type Result = Debug<SomeComplexType>
// 鼠标悬停 Result 即可看到展开后的类型
// 使用工具类型辅助调试
type Prettify<T> = { [K in keyof T]: T[K] } & {}
type Nested = { a: { b: { c: string } }; d: number }
type PrettyNested = Prettify<Nested>
// 展开为 { a: { b: { c: string } }; d: number }
条件类型逐步调试
// 逐步拆解复杂条件类型
type Step1<T> = T extends string ? true : false
type Step2<T> = T extends `${infer H}${infer R}` ? { head: H; rest: R } : never
// 逐层验证
type S1 = Step1<'hello'> // true
type S2 = Step2<'hello'> // { head: 'h'; rest: 'ello' }
常见问题 FAQ
Q1: 类型体操会影响运行时性能吗?
不会。TypeScript 的所有类型信息在编译后都会被完全擦除,类型体操只存在于编译期,对运行时性能零影响。
Q2: 什么时候该用类型体操,什么时候该用运行时校验?
两者配合使用:类型体操负责编译期校验(开发时捕获错误),运行时校验(如 Zod、Joi)负责外部数据(API 响应、用户输入)的校验。类型体操不能替代运行时校验。
Q3: 递归类型深度有限制吗?
有。TypeScript 对递归类型有深度限制(约 1000 层),超出会报「Type instantiation is excessively deep」错误。实际使用中,10 层以内的递归通常足够。
Q4: 如何在团队中推广类型体操?
- 从简单的工具类型开始(Partial、Pick、Omit)
- 建立团队的类型工具库(
types/utils.ts) - 在 Code Review 中要求关键函数有完整类型
- 使用 JSON 格式化工具 验证 JSON 数据结构,配合类型定义确保一致性
Q5: satisfies 和类型注解有什么区别?
类型注解(:)会拓宽类型为注解类型,satisfies 只做校验不改变推断类型。需要保持字面量类型精度时用 satisfies,需要明确约束类型时用类型注解。
💡 使用 Base64 编码工具 处理类型定义中的内联数据,减少外部依赖。
本站提供浏览器本地工具,免注册即可试用 →