从零实现理解TypeScript工具类型

10 阅读4分钟

TypeScript 内置了许多实用的工具类型,帮助我们在类型层面进行各种转换和操作。理解这些工具类型的实现原理,不仅能让我们更好地使用它们,还能学会如何编写自己的类型工具。

1. Partial<T> - 将所有属性变为可选

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

// 使用示例
interface User {
  id: number
  name: string
  age: number
}

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

原理:使用映射类型遍历 T 的所有属性键,并在每个属性后加上 ? 修饰符。

2. Required<T> - 将所有属性变为必选

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

interface Props {
  a?: string
  b?: number
}

type RequiredProps = MyRequired<Props>
// 等价于 { a: string; b: number }

原理-? 语法用于移除属性的可选修饰符。

3. Readonly<T> - 将所有属性变为只读

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

interface Config {
  host: string
  port: number
}

type ReadonlyConfig = MyReadonly<Config>
// 等价于 { readonly host: string; readonly port: number }

4. Pick<T, K> - 从 T 中挑选一组属性

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

interface Person {
  name: string
  age: number
  address: string
}

type NameAndAge = MyPick<Person, 'name' | 'age'>
// 等价于 { name: string; age: number }

原理K extends keyof T 约束 K 必须是 T 的键的子集。

5. Omit<T, K> - 从 T 中排除一组属性

// 基于 Pick 和 Exclude 的实现(推荐)
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

// 直接使用映射类型的实现
type MyOmit2<T, K extends keyof T> = {
  [P in Exclude<keyof T, K>]: T[P]
}

interface Product {
  id: number
  name: string
  price: number
  description: string
}

type ProductPreview = MyOmit<Product, 'description'>
// 等价于 { id: number; name: string; price: number }

6. Exclude<T, U> - 从联合类型 T 中排除 U 中的类型

type MyExclude<T, U> = T extends U ? never : T

type T1 = 'a' | 'b' | 'c'
type T2 = 'a'

type Result = MyExclude<T1, T2>  // 'b' | 'c'

原理:条件类型作用于联合类型时具有分布行为——当 T 是裸类型参数(未被数组、元组等包装)时,条件类型会分别应用于联合类型的每个成员。

7. Extract<T, U> - 从 T 中提取 U 中的类型

type MyExtract<T, U> = T extends U ? T : never

type T1 = 'a' | 'b' | 'c'
type T2 = 'a' | 'd'

type Result = MyExtract<T1, T2>  // 'a'

8. NonNullable<T> - 排除 null 和 undefined

type MyNonNullable<T> = T extends null | undefined ? never : T

type T = string | number | null | undefined
type Result = MyNonNullable<T>  // string | number

9. Record<K, T> - 构建键类型为 K、值类型为 T 的对象类型

type MyRecord<K extends keyof any, T> = {
  [P in K]: T
}

type UserMap = MyRecord<'admin' | 'user', { name: string }>
// 等价于:
// {
//   admin: { name: string }
//   user: { name: string }
// }

10. ReturnType<T> - 获取函数类型的返回值类型

type MyReturnType<T> = T extends (...args: never) => infer R ? R : never

function greet(): string {
  return 'hello'
}

type GreetReturn = MyReturnType<typeof greet>  // string

原理:使用 infer 在条件类型中推断返回值类型。...args: never 能够匹配更多函数类型(包括泛型函数和重载函数)。

11. Parameters<T> - 获取函数类型的参数类型(元组形式)

type MyParameters<T> = T extends (...args: infer P) => any ? P : never

function add(a: number, b: number): number {
  return a + b
}

type AddParams = MyParameters<typeof add>  // [a: number, b: number]

12. InstanceType<T> - 获取构造函数类型的实例类型

type MyInstanceType<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: any) => infer R ? R : never

class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

type PersonInstance = MyInstanceType<typeof Person>  // Person

13. ConstructorParameters<T> - 获取构造函数参数类型

type MyConstructorParameters<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: infer P) => any ? P : never

class Person {
  constructor(name: string, age: number) {}
}

type PersonParams = MyConstructorParameters<typeof Person>  // [name: string, age: number]

14. 高级组合示例:深层 Partial

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

interface Nested {
  user: {
    info: {
      name: string
      age: number
    }
  }
}

type DeepPartialNested = DeepPartial<Nested>
// 所有层级的属性都变成可选的

15. 补充说明:条件类型的分布行为

在使用 ExcludeExtract 等工具类型时,理解条件类型的分布行为至关重要:

// ✅ 会分布 - T 是裸类型参数
type Distribute<T> = T extends string ? true : false

// ❌ 不会分布 - T 被包装在元组中
type NoDistribute<T> = [T] extends [string] ? true : false

type Test = 'a' | 123
type Result1 = Distribute<Test>  // true | false
type Result2 = NoDistribute<Test>  // false(整个联合类型不满足条件)

总结

通过从零实现这些工具类型,我们掌握了 TypeScript 类型编程的核心技巧:

核心语法用途
keyof T获取类型的所有键
[P in K]映射类型,遍历键
? / -?添加/移除可选修饰符
readonly添加只读修饰符
extends条件类型判断
infer在条件类型中推断类型
never表示永不存在的类型
裸类型参数 + extends触发条件类型的分布行为

掌握这些基础构件后,你就可以根据自己的业务需求,编写复杂的类型工具来增强代码的类型安全性了。