TypeScript 高级类型体操

Choyeon· 2026年8月25日· 2 分钟阅读· 254 阅读· 643 字· 949 字符
TypeScript 高级类型体操

TypeScript 的类型系统是图灵完备的。这意味着理论上你可以在类型层面完成任何计算。了解这些高级特性,不仅能看懂开源库的类型定义,还能为自己的代码写出精准的类型推导。

核心工具:条件类型 + infer

如果只能掌握一个特性,那必须是条件类型 + infer。它类似类型层面的三元表达式:

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

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

type DeepRequired<T> = T extends object
  ? { [K in keyof T]-?: DeepRequired<T[K]> }
  : T

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K]
}

type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K]: T[K]
}

type PathOf<T, Prefix extends string = ''> = T extends object
  ? {
      [K in keyof T & string]: K extends string
        ?
            | `${Prefix}${K}`
            | PathOf<T[K], `${Prefix}${K}.`>
        : never
    }[keyof T & string]
  : never

type ValueAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
    ? ValueAtPath<T[K], Rest>
    : never
  : P extends keyof T
  ? T[P]
  : never

type CamelCase<S extends string> = S extends `${infer First}_${infer Rest}`
  ? `${First}${Capitalize<CamelCase<Rest>>}`
  : S

type SnakeCase<S extends string> = S extends `${infer First}${infer Rest}`
  ? First extends Uppercase<First>
    ? `_${Lowercase<First>}${SnakeCase<Rest>}`
    : `${First}${SnakeCase<Rest>}`
  : S

type Length<T extends readonly any[]> = T['length']

type First<T extends readonly any[]> = T extends [infer F, ...any] ? F : never
type Last<T extends readonly any[]> = T extends [...any, infer L] ? L : never

type TupleToUnion<T extends readonly any[]> = T[number]

type UnionToIntersection<U> = (
  U extends any ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never

type IsNever<T> = [T] extends [never] ? true : false
type IsUnion<T, U = T> = IsNever<T> extends true
  ? false
  : T extends U
  ? [U] extends [T]
    ? false
    : true
  : false

type GetReturnType<F> = F extends (...args: any) => infer R ? R : never
type GetParameters<F> = F extends (...args: infer P) => any ? P : never

常见工具类型复杂度分级

难度 类型 核心技巧 实战用途
入门 Partial/Required/Readonly 映射类型 + ?/-?/readonly 每日必用
入门 Pick/Omit/Record/Extract/Exclude as 子句 + keyof + extends 每日必用
中级 ReturnType/Parameters/InstanceType infer 函数签名 类型提取
中级 DeepPartial/DeepReadonly 递归 + 条件类型 配置对象
中级 CamelCase/SnakeCase 模板字面量 + 递归 API 转换层
高级 PathOf/ValueAtPath 递归 + 模板字面量 lodash.get 类型安全
高级 UnionToIntersection 逆变体位置 infer 理论题
地狱 ParseQueryString/Format 多变量递归 类型竞技

分布式条件类型的秘密

当条件类型左边是裸类型参数(naked type parameter)且传入联合类型时,TypeScript 会自动分发:

type Wrap<T> = T extends any ? { value: T } : never
type X = Wrap<string | number>
// 等于 { value: string } | { value: number }
// 因为 string 和 number 分别代入再联合

这是 90% 的人不理解但 90% 的工具类型在用的特性。记住:想让联合"拆开来处理"就用裸泛型,想让联合"整体处理"就用元组包裹 [T] extends [U]。

模板字面量类型

TS 4.1 引入的模板字面量让字符串处理进入了类型层面:

type CSSVar<K extends string> = `--${K}`
type V = CSSVar<'primary-color'>  // '--primary-color'

type Concat<A extends string, B extends string> = `${A}${B}`

最佳实践

  1. 先写出 JS 运行时版本,再"翻译"成类型版本,能极大降低思考难度
  2. 善用 type-challenges 仓库,做 20 道中级题就能熟练大部分套路
  3. 不要在业务代码中滥用,类型体操过深会让同事看不懂;库代码再深挖
  4. 给复杂类型写测试用例,用 Expect<Equal<X, Y>> 范式防止回归
  5. 超过 5 层嵌套时拆分子类型,和正常代码一样要抽象分层

总结

类型体操不是炫技。它的本质是:把原本需要运行时才能发现的错误,提前到编译期;把原本需要手写的类型声明,交给编译器自动推导。在正确的地方(库开发、框架封装、通用工具)使用它,能极大提升代码质量和开发体验。而在业务代码中,保持克制,够用就好。

本文作者

评论 (0)

暂无评论,来抢沙发吧。