TypeScript高级类型实战:条件类型与模板字面量类型深度应用指南

条件类型:类型系统的if-else

TypeScript的条件类型(Conditional Types)在泛型约束和类型推导中扮演着关键角色,语法形式为T extends U ? X : Y,在类型层面实现了条件分支逻辑。很多内置工具类型(Exclude、Extract、ReturnType等)都基于条件类型实现。掌握条件类型的推导规则和分发特性,是编写类型安全代码库的基础能力。

条件类型最关键的特性是distributive conditional types(分布式条件类型)。当检查类型是裸类型参数时,联合类型会自动分发:

// 分布式条件类型示例
type ToArray<T> = T extends any ? T[] : never

type Result = ToArray<string | number>
// 等价于 ToArray<string> | ToArray<number>
// 结果: string[] | number[]

// 禁用分发:用[]包裹T
type ToArrayNoDist<T> = [T] extends [any] ? T[] : never
type Result2 = ToArrayNoDist<string | number>
// 结果: (string | number)[]

分布式行为在实现联合类型过滤时非常有用,但在需要保持联合类型整体性时会造成意外结果。用方括号包裹类型参数可以禁用分发。

infer关键字与类型提取模式

infer在条件类型的extends子句中声明一个待推导的类型变量,TypeScript会根据实际类型自动推断其值:

// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never

type Fn = (x: number) => string
type R = MyReturnType<Fn>  // string

// 提取Promise内部类型
type Unpacked<T> = T extends Promise<infer U> ? U : T
type P = Unpacked<Promise<string[]>>  // string[]

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type E = ElementOf<readonly string[]>  // string

// 多位置infer:提取函数第一个参数类型
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never
type A = FirstArg<(x: number, y: string) => void>  // number

infer声明的类型变量只在条件类型的true分支中可用。在false分支中引用infer变量会报编译错误。多个infer可以在同一个extends中声明,TypeScript会分别推导。

模板字面量类型:字符串级别的类型约束

TypeScript 4.1引入的模板字面量类型(Template Literal Types)将类型系统的能力扩展到字符串层面,可以对字符串的格式、前缀、后缀进行精确约束:

// CSS属性类型安全
type CSSProperty = "margin" | "padding" | "width" | "height" | "color"
type CSSUnit = "px" | "rem" | "em" | "%"
type CSSValue = `${number}${CSSUnit}`

type CSSRule = {
  [K in CSSProperty]?: CSSValue
}

const style: CSSRule = {
  margin: "10px",     // 合法
  width: "100%",      // 合法
  padding: "abc"     // 类型错误: 不匹配 ${number}${CSSUnit}
}
// 事件监听器类型安全
type EventName<T extends string> = `on${Capitalize<T>}`
type EventHandler<T> = (event: T) => void

type ClickEvent = { type: "click"; x: number; y: number }
type InputEvent = { type: "input"; value: string }

type DOMEvents = {
  [EventName<"click">]: EventHandler<ClickEvent>
  [EventName<"input">]: EventHandler<InputEvent>
}
// 结果: { onClick: (event: ClickEvent) => void, onInput: (event: InputEvent) => void }

// 路由参数类型推导
type Route = "/users/:id" | "/posts/:postId/comments/:commentId"
type ExtractParams<S extends string> =
  S extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<Rest>
    : S extends `${string}:${infer Param}`
    ? Param
    : never

type Params = ExtractParams<"/users/:id">          // "id"
type PostParams = ExtractParams<"/posts/:postId/comments/:commentId">
// "postId" | "commentId"

模板字面量类型与CapitalizeUncapitalizeUppercaseLowercase内置工具类型配合,可以构建高度类型安全的事件系统、路由系统和配置系统。

实战:类型安全的API客户端生成器

// 基于接口定义自动生成类型安全的请求方法
interface ApiEndpoints {
  "/api/users": {
    GET: { response: User[]; query: { page?: number } }
    POST: { response: User; body: CreateUserDTO }
  }
  "/api/users/:id": {
    GET: { response: User; params: { id: string } }
    PUT: { response: User; body: UpdateUserDTO; params: { id: string } }
    DELETE: { response: void; params: { id: string } }
  }
}

type Method = "GET" | "POST" | "PUT" | "DELETE"

type ExtractPathParams<Path extends string> =
  Path extends `${string}:${infer Param}`
    ? { [K in Param | ExtractPathParams<Param extends `${infer P}&${string}`
        ? never : Path>]: string }
    : {}

// 类型安全的fetch封装
type SafeFetch = {
  <Path extends keyof ApiEndpoints, M extends keyof ApiEndpoints[Path]>(
    path: Path,
    options: {
      method: M
      params?: ExtractPathParams<Path>
      query?: ApiEndpoints[Path][M] extends { query: infer Q } ? Q : never
      body?: ApiEndpoints[Path][M] extends { body: infer B } ? B : never
    }
  ): Promise<
    ApiEndpoints[Path][M] extends { response: infer R } ? R : never
  >
}

// 使用示例 - 所有参数和返回值都有完整类型推导
const users = await safeFetch("/api/users", { method: "GET", query: { page: 1 } })
// users 类型自动推导为 User[]

const user = await safeFetch("/api/users/:id", { method: "GET", params: { id: "123" } })
// user 类型自动推导为 User

这种模式在大型前端项目中可以彻底消除API调用的类型错误。接口定义变更时,所有相关调用点的类型错误会在编译期暴露,而不是在运行时才发现字段名拼写错误或参数遗漏。

类型体操的性能边界

复杂条件类型在深度递归时会导致TypeScript编译器性能下降甚至栈溢出。实际项目中的经验值:递归深度超过10层的条件类型,编译时间会从毫秒级跳到秒级;超过20层可能触发TypeScript的递归深度限制。拆解方法:将深层嵌套的条件类型拆分为多层中间类型别名,每层只处理一层逻辑。对于超过10层的类型推导场景,考虑用代码生成替代纯类型体操。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-shi-zhan-tiao-jian-lei-xing-yu/

(0)
小编小编
上一篇 10小时前
下一篇 10小时前

相关推荐