TypeScript 的泛型是其类型系统中最强大的特性之一。本文将深入探讨泛型的高级用法,帮助你写出更加类型安全和灵活的代码。
基础泛型回顾
在深入高级用法之前,让我们快速回顾一下基础泛型:
TYPESCRIPT
// 基础泛型函数
function identity<T>(arg: T): T {
return arg
}
// 泛型接口
interface Container<T> {
value: T
}
// 泛型类
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
条件类型
条件类型允许我们根据类型关系来选择类型:
TYPESCRIPT
// 基础条件类型
type IsString<T> = T extends string ? true : false
type Test1 = IsString<string> // true
type Test2 = IsString<number> // false
// 更复杂的条件类型
type NonNullable<T> = T extends null | undefined ? never : T
type SafeString = NonNullable<string | null> // string
分布式条件类型
当条件类型作用于联合类型时,会分布式地应用:
TYPESCRIPT
type ToArray<T> = T extends any ? T[] : never
type StringOrNumberArray = ToArray<string | number>
// 结果: string[] | number[]
映射类型
映射类型允许我们基于旧类型创建新类型:
TYPESCRIPT
// 基础映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
type Partial<T> = {
[P in keyof T]?: T[P]
}
// 高级映射类型
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
}
interface Person {
name: string
age: number
}
type PersonGetters = Getters<Person>
// 结果: { getName: () => string; getAge: () => number }
模板字面量类型
TypeScript 4.1 引入了模板字面量类型:
TYPESCRIPT
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickEvent = EventName<'click'> // 'onClick'
// 组合使用
type AllEventNames<T extends Record<string, any>> = {
[K in keyof T]: EventName<string & K>
}[keyof T]
interface Events {
click: MouseEvent
hover: MouseEvent
focus: FocusEvent
}
type EventHandlers = AllEventNames<Events>
// 'onClick' | 'onHover' | 'onFocus'
实用工具类型实现
让我们看看如何实现一些常用的工具类型:
Pick 和 Omit
TYPESCRIPT
// 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]
}
// 或者使用 Pick 和 Exclude
type MyOmit2<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
DeepPartial
TYPESCRIPT
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P]
}
interface NestedObject {
a: {
b: {
c: string
}
}
}
type PartialNested = DeepPartial<NestedObject>
// { a?: { b?: { c?: string } } }
ReturnType 实现
TYPESCRIPT
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any
function example(): string {
return 'hello'
}
type ExampleReturn = MyReturnType<typeof example> // string
高级泛型模式
函数重载与泛型
TYPESCRIPT
function createElement<T extends keyof HTMLElementTagNameMap>(
tag: T
): HTMLElementTagNameMap[T]
function createElement<T extends React.ComponentType<any>>(
component: T
): React.ReactElement<React.ComponentProps<T>>
function createElement(tagOrComponent: any) {
// 实现
}
// 使用
const div = createElement('div') // HTMLDivElement
const button = createElement('button') // HTMLButtonElement
泛型约束的链式调用
TYPESCRIPT
class QueryBuilder<T = any> {
private conditions: string[] = []
where<K extends keyof T>(
field: K,
operator: string,
value: T[K]
): QueryBuilder<T> {
this.conditions.push(`${String(field)} ${operator} ${value}`)
return this
}
select<K extends keyof T>(...fields: K[]): QueryBuilder<Pick<T, K>> {
// 实现选择逻辑
return this as any
}
build(): string {
return this.conditions.join(' AND ')
}
}
// 使用
interface User {
id: number
name: string
email: string
}
const query = new QueryBuilder<User>()
.where('name', '=', 'John')
.where('id', '>', 0)
.select('name', 'email')
.build()
类型体操实战
数组转元组
TYPESCRIPT
type ArrayToTuple<T extends readonly any[]> = T extends readonly [
infer First,
...infer Rest
]
? [First, ...ArrayToTuple<Rest>]
: []
const arr = ['a', 'b', 'c'] as const
type Tuple = ArrayToTuple<typeof arr> // ['a', 'b', 'c']
字符串操作
TYPESCRIPT
// 字符串反转
type Reverse<S extends string> = S extends `${infer First}${infer Rest}`
? `${Reverse<Rest>}${First}`
: ''
type Reversed = Reverse<'hello'> // 'olleh'
// 驼峰转下划线
type CamelToSnake<S extends string> = S extends `${infer T}${infer U}`
? `${T extends Capitalize<T> ? '_' : ''}${Lowercase<T>}${CamelToSnake<U>}`
: S
type SnakeCase = CamelToSnake<'getUserName'> // 'get_user_name'
性能注意事项
避免深度递归
TYPESCRIPT
// 可能导致性能问题的深度递归
type BadDeepObject<T, D extends number = 10> = {
[K in keyof T]: D extends 0
? T[K]
: T[K] extends object
? BadDeepObject<T[K], Prev<D>>
: T[K]
}
// 更好的方式:限制递归深度或使用不同的方法
type GoodDeepObject<T> = {
[K in keyof T]: T[K] extends object
? { [P in keyof T[K]]: T[K][P] } // 只递归一层
: T[K]
}
总结
TypeScript 泛型是一个强大的工具,可以帮助我们:
- 创建可重用的类型 - 通过泛型参数适应不同的使用场景
- 保持类型安全 - 在编译时捕获类型错误
- 改善开发体验 - 提供更好的智能提示和重构支持
掌握这些高级泛型技巧,将让你的 TypeScript 代码更加优雅和强大。记住,复杂的类型系统虽然强大,但也要考虑可读性和维护性。