WSの小屋

TypeScript 高级类型:从入门到精通

TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面做几乎任何计算。本文不是罗列 API,而是通过实际场景,展示如何用高级类型解决真实工程问题。

条件类型:类型层面的 if-else

基础语法

type IsString<T> = T extends string ? true : false

type A = IsString<'hello'>  // true
type B = IsString<123>       // false
type C = IsString<string | number>  // boolean (分布式条件类型)

分布式条件类型

T 是联合类型时,条件类型会「分发」到每个成员:

type ToArray<T> = T extends any ? T[] : never

type Result = ToArray<string | number>
// string[] | number[]

// 阻止分发:用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type Result2 = ToArrayNonDist<string | number>
// (string | number)[]

实战:提取 API 响应类型

interface ApiResponses {
  user: { data: User; error: null }
  post: { data: Post; error: null }
  error: { data: null; error: string }
}

// 提取成功的响应数据类型
type SuccessData<T> =
  T extends { data: infer D; error: null } ? D : never

type UserOrPost = SuccessData<ApiResponses['user'] | ApiResponses['post'] | ApiResponses['error']>
// User | Post

映射类型:类型层面的 for-in

内置工具类型的本质

// Readonly 的实现
type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

// Partial 的实现
type MyPartial<T> = {
  [P in keyof T]?: T[P]
}

// Pick 的实现(结合条件类型)
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

实战:API 字段映射

场景:后端返回 snake_case,前端用 camelCase。需要一个类型工具自动映射。

type SnakeToCamel<S extends string> =
  S extends `${infer Head}_${infer Tail}`
    ? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
    : S

type CamelToSnake<S extends string> =
  S extends `${infer Head}${infer Tail}`
    ? `${Head extends Capitalize<Head> ? `_${Lowercase<Head>}` : Head}${CamelToSnake<Tail>}`
    : S

// 自动转换整个对象的 key
type SnakeToCamelObj<T> = {
  [K in keyof T as SnakeToCamel<K & string>]: T[K]
}

// 使用
interface BackendUser {
  user_id: number
  user_name: string
  created_at: string
  is_active: boolean
}

type FrontendUser = SnakeToCamelObj<BackendUser>
// {
//   userId: number
//   userName: string
//   createdAt: string
//   isActive: boolean
// }

修饰符的增删

// 移除 readonly
type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}

// 移除可选
type Required<T> = {
  [P in keyof T]-?: T[P]
}

// 将特定 key 变为可选
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

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

type UserWithOptionalEmail = Optional<User, 'email'>
// { id: number; name: string; email?: string }

infer:类型层面的变量声明

函数返回值提取

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type MyParameters<T> = T extends (...args: infer P) => any ? P : never
type Awaited<T> = T extends Promise<infer U> ? U : T

// 嵌套 Promise 解包
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T

type R1 = DeepAwaited<Promise<Promise<Promise<number>>>>  // number

实战:提取数组元素类型

type ElementOf<T> = T extends (infer E)[] ? E : never

type R = ElementOf<['a', 'b', 'c']>  // 'a' | 'b' | 'c'
type N = ElementOf<number[]>          // number

实战:提取组件 Props 类型

import type { Component } from 'vue'

// 从 Vue 组件中提取 Props 类型
type ComponentProps<T> = T extends new () => { $props: infer P } ? P : never

// 从组件定义中提取
const MyComponent = defineComponent({
  props: {
    title: String,
    count: { type: Number, required: true }
  }
})

type MyProps = ComponentProps<typeof MyComponent>

模板字面量类型

基础用法

type Greeting = `Hello, ${string}!`
const msg: Greeting = 'Hello, World!'  // ✅
const err: Greeting = 'Hi'             // ❌

// 事件处理器名称
type Events = 'click' | 'hover' | 'focus'
type Handlers = `on${Capitalize<Events>}`
// "onClick" | "onHover" | "onFocus"

实战:类型安全的路由系统

interface RouteMap {
  '/': { page: 'home' }
  '/users': { page: 'users' }
  '/users/:id': { page: 'user'; params: { id: string } }
  '/posts/:id/comments': { page: 'comments'; params: { id: string } }
}

// 提取所有路由路径
type RoutePath = keyof RouteMap

// 提取带参数的路由路径
type DynamicRoute = {
  [K in RoutePath]: K extends `${string}:${string}` ? K : never
}[RoutePath]
// "/users/:id" | "/posts/:id/comments"

// 参数提取
type ExtractParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
      ? { [K in Param]: string }
      : {}

type UserParams = ExtractParams<'/users/:id'>
// { id: string }
type CommentParams = ExtractParams<'/posts/:id/comments'>
// { id: string }

类型递归与深度操作

DeepPartial

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

interface Config {
  app: {
    name: string
    version: string
  }
  server: {
    port: number
    host: string
  }
}

type PartialConfig = DeepPartial<Config>
// 所有层级都变成可选

DeepReadonly

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

路径类型:类型安全的对象访问

type Path<T> = T extends object
  ? {
      [K in keyof T & string]:
        | K
        | `${K}.${Path<T[K]> extends infer P ? P extends string ? P : never : never}`
    }[keyof T & string]
  : never

type ConfigPath = Path<Config>
// "app" | "server" | "app.name" | "app.version" | "server.port" | "server.host"

// 类型安全的 get 函数
function get<T, P extends Path<T>>(obj: T, path: P): unknown {
  return path.split('.').reduce((acc: any, key) => acc?.[key], obj)
}

const config: Config = { /* ... */ }
const name = get(config, 'app.name')     // ✅
// get(config, 'app.invalid')             // ❌ 类型错误

类型守卫与自定义类型保护

类型谓词

function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function process(value: unknown) {
  if (isString(value)) {
    value.toUpperCase()  // TS 知道这里 value 是 string
  }
}

实战:Discriminated Union 守卫

type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E }

function isOk<T, E>(result: Result<T, E>): result is { success: true; data: T } {
  return result.success
}

function handleResult(result: Result<string>) {
  if (isOk(result)) {
    console.log(result.data.toUpperCase())  // ✅ data 是 string
  } else {
    console.log(result.error.message)  // ✅ error 是 Error
  }
}

断言函数

function assertNonNull<T>(value: T): asserts value is NonNullable<T> {
  if (value === null || value === undefined) {
    throw new Error(`Expected non-null, got ${value}`)
  }
}

const maybeUser: User | null = fetchUser()
assertNonNull(maybeUser)
// 此处 maybeUser 被推断为 User

实用类型工具集

// 联合类型转交叉类型
type UnionToIntersection<U> =
  (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never

// 获取函数的第一个参数类型
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never

// 标记某些属性为必填
type RequireKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>

// 标记某些属性为只读
type ReadonlyKeys<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>

// 非空数组
type NonEmptyArray<T> = [T, ...T[]]

// 固定长度的元组
type FixedLengthArray<T, N extends number, A extends T[] = []> =
  A['length'] extends N ? A : FixedLengthArray<T, N, [...A, T]>

type Triple = FixedLengthArray<string, 3>  // [string, string, string]

类型体操实战:类型安全的 EventEmitter

type EventMap = {
  click: { x: number; y: number }
  scroll: { top: number }
  resize: { width: number; height: number }
}

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

  on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
    (this.handlers[event] ??= []).push(handler)
  }

  off<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void {
    this.handlers[event] = this.handlers[event]?.filter(h => h !== handler)
  }

  emit<K extends keyof T>(event: K, payload: T[K]): void {
    this.handlers[event]?.forEach(h => h(payload))
  }
}

const emitter = new TypedEmitter<EventMap>()

emitter.on('click', ({ x, y }) => console.log(x, y))  // ✅ 类型安全
emitter.emit('click', { x: 10, y: 20 })                  // ✅
// emitter.emit('click', { x: 10 })                       // ❌ 缺少 y
// emitter.on('unknown', () => {})                        // ❌ 不存在的事件

总结

TypeScript 高级类型的核心思维是「类型也是程序」:

  • 条件类型 是 if-else
  • 映射类型 是 for-in
  • infer 是变量声明
  • 模板字面量类型 是字符串操作
  • 递归类型 是循环

掌握了这些,你就能写出类型安全的路由系统、API 请求层、状态管理——把运行时错误在编译阶段消灭。建议从实际需求出发,每次遇到重复的类型逻辑,就尝试用高级类型来抽象。

Comments | 0条评论