发布于

TypeScript 进阶技巧:类型体操与实战应用

作者

TypeScript 进阶技巧:类型体操与实战应用

TypeScript 不仅仅是 JavaScript 的超集,它还提供了强大的类型系统。本文将深入探讨 TypeScript 的高级特性,帮助你写出更安全、更优雅的代码。

高级类型系统

1. 条件类型(Conditional Types)

条件类型是 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 ApiResponse<T> = T extends string
  ? { message: T }
  : T extends number
    ? { code: T }
    : { data: T }

type StringResponse = ApiResponse<string> // { message: string }
type NumberResponse = ApiResponse<number> // { code: number }
type ObjectResponse = ApiResponse<{ id: number }> // { data: { id: number } }

2. 映射类型(Mapped Types)

映射类型允许我们基于现有类型创建新类型。

// 基础映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

type Partial<T> = {
  [P in keyof T]?: T[P]
}

// 高级映射类型示例
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

interface User {
  name: string
  age: number
  email: string
}

type UserGetters = Getters<User>
// {
//   getName: () => string;
//   getAge: () => number;
//   getEmail: () => string;
// }

// 深度只读类型
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}

3. 模板字面量类型(Template Literal Types)

模板字面量类型让我们可以在类型层面操作字符串。

// 基础模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`

type ClickEvent = EventName<'click'> // 'onClick'
type HoverEvent = EventName<'hover'> // 'onHover'

// 复杂的模板字面量类型
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/posts' | '/comments'

type APIRoute = `${HTTPMethod} ${Endpoint}`
// 'GET /users' | 'GET /posts' | 'GET /comments' |
// 'POST /users' | 'POST /posts' | 'POST /comments' | ...

// 路径参数提取
type ExtractParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}`
  ? Param | ExtractParams<`/${Rest}`>
  : T extends `${infer _Start}:${infer Param}`
    ? Param
    : never

type Params = ExtractParams<'/users/:id/posts/:postId'> // 'id' | 'postId'

实用工具类型

1. 类型安全的事件处理

// 类型安全的事件系统
type EventMap = {
  'user:login': { userId: string; timestamp: number }
  'user:logout': { userId: string }
  'post:create': { postId: string; title: string }
  'post:delete': { postId: string }
}

class TypedEventEmitter<T extends Record<string, any>> {
  private listeners: {
    [K in keyof T]?: Array<(data: T[K]) => void>
  } = {}

  on<K extends keyof T>(event: K, listener: (data: T[K]) => void) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event]!.push(listener)
  }

  emit<K extends keyof T>(event: K, data: T[K]) {
    const eventListeners = this.listeners[event]
    if (eventListeners) {
      eventListeners.forEach((listener) => listener(data))
    }
  }
}

const emitter = new TypedEventEmitter<EventMap>()

// 类型安全的事件监听
emitter.on('user:login', (data) => {
  console.log(`用户 ${data.userId}${data.timestamp} 登录`)
})

// 类型安全的事件发射
emitter.emit('user:login', {
  userId: '123',
  timestamp: Date.now(),
})

2. 类型安全的 API 客户端

// API 路由定义
type APIRoutes = {
  'GET /users': {
    response: { users: User[] }
  }
  'GET /users/:id': {
    params: { id: string }
    response: { user: User }
  }
  'POST /users': {
    body: { name: string; email: string }
    response: { user: User }
  }
  'PUT /users/:id': {
    params: { id: string }
    body: Partial<User>
    response: { user: User }
  }
}

// 提取路由信息的工具类型
type ExtractRouteInfo<T extends keyof APIRoutes> = APIRoutes[T]

type HasParams<T> = T extends { params: any } ? T['params'] : never
type HasBody<T> = T extends { body: any } ? T['body'] : never
type GetResponse<T> = T extends { response: any } ? T['response'] : never

// 类型安全的 API 客户端
class APIClient {
  async request<T extends keyof APIRoutes>(
    route: T,
    ...args: HasParams<APIRoutes[T]> extends never
      ? HasBody<APIRoutes[T]> extends never
        ? []
        : [{ body: HasBody<APIRoutes[T]> }]
      : HasBody<APIRoutes[T]> extends never
        ? [{ params: HasParams<APIRoutes[T]> }]
        : [{ params: HasParams<APIRoutes[T]>; body: HasBody<APIRoutes[T]> }]
  ): Promise<GetResponse<APIRoutes[T]>> {
    // 实际的 HTTP 请求逻辑
    return {} as GetResponse<APIRoutes[T]>
  }
}

const api = new APIClient()

// 类型安全的 API 调用
const users = await api.request('GET /users')
const user = await api.request('GET /users/:id', { params: { id: '123' } })
const newUser = await api.request('POST /users', {
  body: { name: 'John', email: 'john@example.com' },
})

3. 状态机类型

// 状态机类型定义
type StateMachine<States extends string, Events extends string> = {
  [S in States]: {
    [E in Events]?: States
  }
}

// 订单状态机
type OrderState = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'
type OrderEvent = 'process' | 'ship' | 'deliver' | 'cancel'

const orderStateMachine: StateMachine<OrderState, OrderEvent> = {
  pending: {
    process: 'processing',
    cancel: 'cancelled',
  },
  processing: {
    ship: 'shipped',
    cancel: 'cancelled',
  },
  shipped: {
    deliver: 'delivered',
  },
  delivered: {},
  cancelled: {},
}

// 类型安全的状态转换
class OrderStateMachine {
  constructor(private state: OrderState = 'pending') {}

  transition(event: OrderEvent): boolean {
    const nextState = orderStateMachine[this.state][event]
    if (nextState) {
      this.state = nextState
      return true
    }
    return false
  }

  getState(): OrderState {
    return this.state
  }
}

类型体操实战

1. 深度合并类型

type DeepMerge<T, U> = {
  [K in keyof T | keyof U]: K extends keyof U
    ? K extends keyof T
      ? T[K] extends object
        ? U[K] extends object
          ? DeepMerge<T[K], U[K]>
          : U[K]
        : U[K]
      : U[K]
    : K extends keyof T
      ? T[K]
      : never
}

type Config1 = {
  api: {
    baseUrl: string
    timeout: number
  }
  features: {
    darkMode: boolean
  }
}

type Config2 = {
  api: {
    timeout: number
    retries: number
  }
  features: {
    notifications: boolean
  }
}

type MergedConfig = DeepMerge<Config1, Config2>
// {
//   api: {
//     baseUrl: string;
//     timeout: number;
//     retries: number;
//   };
//   features: {
//     darkMode: boolean;
//     notifications: boolean;
//   };
// }

2. 函数重载类型

// 函数重载类型定义
type Overload<T extends (...args: any[]) => any> = T extends {
  (...args: infer A1): infer R1
  (...args: infer A2): infer R2
  (...args: infer A3): infer R3
}
  ? {
      (...args: A1): R1
      (...args: A2): R2
      (...args: A3): R3
    }
  : T extends {
        (...args: infer A1): infer R1
        (...args: infer A2): infer R2
      }
    ? {
        (...args: A1): R1
        (...args: A2): R2
      }
    : T

// 使用示例
function createElement(tag: 'div'): HTMLDivElement
function createElement(tag: 'span'): HTMLSpanElement
function createElement(tag: 'input'): HTMLInputElement
function createElement(tag: string): HTMLElement {
  return document.createElement(tag)
}

type CreateElement = typeof createElement
// 自动推断出所有重载签名

最佳实践

1. 类型守卫

// 类型守卫函数
function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'name' in obj &&
    'email' in obj &&
    typeof (obj as any).name === 'string' &&
    typeof (obj as any).email === 'string'
  )
}

// 使用类型守卫
function processData(data: unknown) {
  if (isString(data)) {
    // data 的类型现在是 string
    console.log(data.toUpperCase())
  } else if (isUser(data)) {
    // data 的类型现在是 User
    console.log(`Hello, ${data.name}!`)
  }
}

2. 品牌类型

// 品牌类型用于区分相同基础类型的不同用途
type UserId = string & { readonly brand: unique symbol }
type PostId = string & { readonly brand: unique symbol }

function createUserId(id: string): UserId {
  return id as UserId
}

function createPostId(id: string): PostId {
  return id as PostId
}

function getUser(id: UserId): User {
  // 实现获取用户逻辑
  return {} as User
}

const userId = createUserId('user-123')
const postId = createPostId('post-456')

getUser(userId) // ✅ 正确
// getUser(postId); // ❌ 类型错误

总结

TypeScript 的高级特性为我们提供了强大的类型安全保障:

  1. 条件类型:根据条件选择类型
  2. 映射类型:基于现有类型创建新类型
  3. 模板字面量类型:类型层面的字符串操作
  4. 工具类型:提高开发效率和类型安全
  5. 类型体操:解决复杂的类型推导问题

掌握这些技巧,你就能充分发挥 TypeScript 的威力,写出更安全、更优雅的代码!


TypeScript 的类型系统非常强大,值得我们深入学习和实践。希望这篇文章对你有所帮助!