阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 类型编程最佳实践

类型编程最佳实践

作者:陈川 阅读数:41177人阅读 分类: TypeScript

类型编程是 TypeScript 的核心能力之一,通过合理运用类型系统,可以显著提升代码的健壮性和可维护性。从基础类型操作到高级模式匹配,类型编程的实践方式多种多样,关键在于理解类型系统的底层逻辑和实际应用场景。

基础类型操作与工具类型

TypeScript 内置了大量工具类型,合理使用它们能减少重复代码。例如 Partial<T> 可以将所有属性变为可选:

interface User {
  id: number
  name: string
}

type PartialUser = Partial<User>
// 等价于 { id?: number; name?: string }

PickOmit 是处理对象类型的利器:

type UserName = Pick<User, 'name'> // { name: string }
type UserWithoutId = Omit<User, 'id'> // { name: string }

自定义工具类型时,条件类型是基础构建块:

type Nullable<T> = T | null
type ValueOf<T> = T[keyof T]

类型推断与模式匹配

infer 关键字在解构复杂类型时非常有用。例如提取函数返回类型:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function getUser() { return { id: 1 } }
type UserReturn = ReturnType<typeof getUser> // { id: number }

处理数组类型时,递归类型表现出色:

type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T
type Nested = number[][][]
type Flat = Flatten<Nested> // number

模板字面量类型实战

4.1 版本引入的模板字面量类型可以创建精细的字符串约束:

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type ApiPath = `/${string}`

type Endpoint = `${HttpMethod} ${ApiPath}`
const endpoint: Endpoint = 'GET /users' // 合法

结合映射类型可以实现动态属性生成:

type EventMap = {
  click: MouseEvent
  focus: FocusEvent
}

type HandlerMap = {
  [K in keyof EventMap as `on${Capitalize<K>}`]: (event: EventMap[K]) => void
}
// 生成 { onClick: (event: MouseEvent) => void; onFocus: (event: FocusEvent) => void }

类型守卫与可辨识联合

自定义类型守卫能显著改善类型窄化:

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every(item => typeof item === 'string')
}

const data: unknown = ['a', 'b']
if (isStringArray(data)) {
  data.map(s => s.toUpperCase()) // 安全调用
}

可辨识联合(Discriminated Unions)是处理复杂状态的最佳实践:

type Result<T> = 
  | { status: 'success'; data: T }
  | { status: 'error'; message: string }

function handleResult(result: Result<number>) {
  switch (result.status) {
    case 'success':
      console.log(result.data * 2) // 自动窄化
      break
    case 'error':
      console.error(result.message)
      break
  }
}

高级模式:类型级编程

实现类型安全的 Curry 函数需要深度类型操作:

type Curry<F> = F extends (...args: infer A) => infer R
  ? A extends [infer First, ...infer Rest]
    ? (arg: First) => Curry<(...args: Rest) => R>
    : R
  : never

function curry<F>(fn: F): Curry<F> {
  // 实现省略
}

const add = (a: number, b: number) => a + b
const curriedAdd = curry(add)
const add5 = curriedAdd(5) // (b: number) => number

类型谓词可以创建复杂的验证逻辑:

type IPv4 = `${number}.${number}.${number}.${number}`

function isIPv4(str: string): str is IPv4 {
  const parts = str.split('.')
  return parts.length === 4 && parts.every(part => {
    const num = parseInt(part, 10)
    return num >= 0 && num <= 255 && part === num.toString()
  })
}

性能优化与类型缓存

复杂类型运算可能导致编译器性能下降,这时需要类型缓存:

type DeepReadonly<T> = 
  T extends Function ? T :
  T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
  T

// 使用接口缓存中间类型
interface ReadonlyUser extends DeepReadonly<User> {}

避免过度使用递归类型,必要时设置深度限制:

type DeepPartial<T, Depth extends number = 3> = 
  [Depth] extends [0] ? T :
  T extends object ? { [K in keyof T]?: DeepPartial<T[K], [-1, 0, 1, 2][Depth]> } : 
  T

类型与运行时值的同步

通过 const assertions 保持类型与值同步:

const routes = ['home', 'users', 'settings'] as const
type Route = typeof routes[number] // "home" | "users" | "settings"

创建类型安全的枚举替代方案:

function createEnum<T extends string>(...values: T[]) {
  return Object.freeze(values.reduce((acc, val) => {
    acc[val] = val
    return acc
  }, {} as { [K in T]: K }))
}

const Direction = createEnum('North', 'South', 'East', 'West')
type Direction = keyof typeof Direction // "North" | "South" | "East" | "West"

类型系统边界处理

处理动态数据结构时需要灵活的类型设计:

type JSONValue = 
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue }

function safeParse(json: string): unknown {
  try {
    return JSON.parse(json)
  } catch {
    return undefined
  }
}

const data = safeParse('{"a":1}')
if (data && typeof data === 'object' && 'a' in data) {
  // 类型窄化处理
}

处理第三方库类型扩展时,模块增强是标准做法:

declare module 'some-library' {
  interface Config {
    customOption?: boolean
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌