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 泛型是一个强大的工具,可以帮助我们:

  1. 创建可重用的类型 - 通过泛型参数适应不同的使用场景
  2. 保持类型安全 - 在编译时捕获类型错误
  3. 改善开发体验 - 提供更好的智能提示和重构支持

掌握这些高级泛型技巧,将让你的 TypeScript 代码更加优雅和强大。记住,复杂的类型系统虽然强大,但也要考虑可读性和维护性。