Skip to content

内置工具类型

1. 工具类型的分类

内置的工具类型按照类型操作的不同,可以大致划分为这么几类:

信息

  • 对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。我们将这一类统称为属性修饰工具类型
  • 对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。我们将这一类统称为结构工具类型
  • 对集合(即联合类型)的处理,即交集、并集、差集、补集。我们将这一类统称为集合工具类型
  • 基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。我们将其统称为模式匹配工具类型
  • 模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。这一类当然就统称为模板字符串工具类型

2. 属性修饰工具类型

1. Partial

Partial<Type>用于构造一个Type下面的所有属性都设置为可选的类型,这个工具类型会返回代表给定的一个类型的子集的类型。

ts
interface IPerson {
  name?: string
  age?: number
}

type p = Partial<IPerson>
/*
    type p = {
        name?: string | undefined;
        age?: number | undefined;
    }
*/
Partial实现
ts
/**
 * in 操作符遍历传入类型的键所组成的联合类型(keyof 的返回类型),添加可选符号依次返回
 */
type myPartial<T> = {
  [k in keyof T]?: T[k]
}

2. Required

Required<Type>用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial相反。

ts
interface Person {
  name?: string
  age?: number
}
type p = Required<Person>
/*
    type p = {
        name: string;
        age: number;
    }
*/
Required实现
ts
/**
 * 遍历键联合类型,依次删除可选操作符 返回当前键值的类型
 */
type myRequired<T> = {
  [k in keyof T]-?: T[k]
}

3. ReadOnly

ReadOnly<Type>用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。

ts
interface Person {
  name: string
  age: number
}
type p = Readonly<Person>
/*
    type p = {
        readonly name: string;
        readonly age: number;
    }
*/
ReadOnly实现
ts
/**
 * 遍历键联合类型,依次添加只读操作符
 */
type myReadOnly<T> = {
  readonly [k in keyof T]: T[k]
}

4. Mutable(自定义)

typeScript没有内置将类型只读属性全部转换为普通属性的方法,这里可以自定义一个:Mutable<Type>用于构造一个Type下面的所有只读属性全都设置为可修改的类型,意味着这个类型的所有的属性全都可以重新赋值。 实现:

ts
/**
 * 遍历泛型键的联合类型 去除readonly修饰符
 */
type Mutable<T> = {
  -readonly [k in keyof T]: T[k]
}

示例:

ts
interface IPerson1 {
  readonly name: string
  readonly age: number
}

type Mutable<T> = {
  -readonly [k in keyof T]: T[k]
}
type m = Mutable<IPerson1>

/*
    type m = {
        name: string;
        age: number;
    }
*/

5. 深度添加修饰符(自定义)

深度添加修饰符:通过将类型进行递归来深度操作对象类型的属性

ts
/* 对象类型深度可选 */
type DeepPartial<T extends object> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
}
/* 深度必须属性 */
type DeepRequired<T extends object> = {
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
}
/* 深度只读 */
type DeepReadonly<T extends object> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
}
/* 深度可写 */
type DeepMutable<T extends object> = {
  -readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
}

3. 结构工具类型

1. Record

Record<keys , Type>用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。这个工具类型可以被用于映射一个类型的属性到另一个类型。

ts
interface CatInfo {
  age: number
  breed: string
}

type CatName = 'miffy' | 'boris' | 'mordred'

const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: 'Persian' },
  boris: { age: 5, breed: 'Maine Coon' },
  mordred: { age: 16, breed: 'British Shorthair' },
}
/* 一个任意键值的对象类型 ,效果类似索引签名 */
type c = Record<string, any>

/*
 type c = {
    [key:string]:any
 }
*/
Record实现
ts
/**
 *  string | number | symbol 就是js中对象键所拥有的类型
 */
type Record<K extends string | number | symbol, T> = {
  [P in K]: T
}

注意

Record实现中,传入的类型参数 K被限制为string | number | symbol(js对象键的类型),而上例的代码中,传入的K的类型为:"miffy" | "boris" | "mordred"(字面量类型所组成的联合类型),从层级链可知:

  • 相同原始类型字面量所组成的联合类型 < 该原始类型 "miffy" | "boris" | "mordred" < string

  • 原始类型 < 包含该原始类型所组成的联合类型 string < 'string' | 'number' | 'boolean'

    所以 "miffy" | "boris" | "mordred"类型是 'string' | 'number' | 'boolean'的子类型

2. Pick

Pick<Type, Keys>用于构造一个类型,它是从Type类型里面挑了一些属性Keys(Keys字符串字面量 或者 字符串字面量的联合类型),其中Keys受到 keyof Type约束

ts
interface IPerson {
  name: string
  age: number
  hobby: string[]
}

type a = Pick<IPerson, 'age'>
/*
    type a = {
        age: number;
    }
*/
type p = Pick<IPerson, 'age' | 'name'>

/*
    type p = {
        age: number;
        name: string;
    }
*/
Pick实现
ts
type Pick<T, K extends keyof T> = {
  [p in K]: T[p]
}

3. Omit

Omit<Type, Keys> 用于构造一个类型,它是从Type类型里面过滤了一些属性Keys(Keys字符串字面量 或者 字符串字面量的联合类型),其中Keys不受 keyof Type约束

ts
interface IPerson {
  name: string
  age: number
  hobby: string[]
}

type p = Omit<IPerson, 'hobby'>
/*
    type p = {
        name: string;
        age: number;
    }
*/

type a = Omit<IPerson, 'hobby' | 'name'>

/*
    type a = {
        age: number;
    }
*/
Omit实现
ts
/**
 * 借助Pick Exclude
 * Exclude: 从联合类型中剔除指定的类型
 * type p = string | number | boolean | null
 * type s = Exclude<p , null | boolean>  // string | number
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

/* 借助Exclude */
type Omit<T, K extends keyof string | number | symbol> = {
  [p in Exclude<keyof T, K>]: T[p]
}

4. 集合工具类型

集合工具类型主要使用条件类型、条件类型分布式特性实现。

1. Extract(交集)

Extract<Type , Union> 用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型。

ts
type a = 1 | 2 | 3
type b = 2 | 3 | 5

type x = Extract<a, b> // 2 | 3
/* 根据分布式条件特性, 以上代码相当于: */
type x =
    (1 extends 2 | 3 | 5 ? 1 : never) // never
    | (2 extends 2 | 3 | 5 ? 2 : never) // 2
    | (3 extends 2 | 3 | 5 ? 3 : never) // 3
Extract实现
ts
/**
 * 泛型参数T为联合类型的时候,分布式条件特性起作用,拆分T依次执行条件类型,
 * 最终结果组成新的联合类型返回
 */
type Extract<T, U> = T extends U ? T : never

2. Exclude(差集)

Exclude<UnionType, ExcludedMembers>用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型。

ts
type unioType = 1 | 2 | 3
//  type c = 1 | 2
type c = Exclude<unioType, 3 | 5>

/*根据分布式条件特性, 以上代码相当于: */
type c =
    (1 extends 3 | 5 ? never : 1) // 1
    | (2 extends 3 | 5 ? never : 2) // 2
    | (3 extends 3 | 5 ? never | 3) //never
Exclude实现
ts
// 与Extract相反
type Exclude<T, U> = T extends U ? never : T

3. NonNullable

NonNullable<Type>用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型。

ts
type Res = NonNullable<null | undefined | string | { name: string }>
/*
type Res = string | {
    name: string;
}
*/
NonNullable实现
ts
type NonNullable<T> = T & {}

type NonNullable<T> = Exclude<T, undefined | null>

5. 模式匹配工具类型

模式匹配工具类型主要使用条件类型 与 infer 关键字实现。

1. Parameters

Parameters<Type>用于根据所有Type中函数类型的参数构造一个元祖类型。

ts
type Fun = (a: string, b: number) => void

// type c = [a: string, b: number]
type c = Parameters<Fun>
Parameters实现
ts
type FuncType = (...args: any[]) => any
/* infer 推断args剩余参数类型返回 */
type Parameters<T extends FuncType> = T extends (...args: infer P) => any ? P : never

2. ReturnType

ReturnType<Type> 用于构造一个含有Type函数的返回值的类型。

ts
type Fun = (a: string, b: number) => string
// string
type x = ReturnType<Fun>
ReturnType实现
ts
type FuncType = (...args: any[]) => any
/* infer 推断返回值类型返回 */
type ReturnType<T extends FuncType> = T extends (...args: any) => infer R ? R : any

6. 基于已知属性进行部分修饰

内置的工具类型 Partial,ReadOnly,Required,包括之前的自定义深度操作都是全量取操作对象类型属性,可以实现指定属性名来添加访问符:

ts
/* 测试对象类型 */
interface IPerson {
  name: string
  age: number
  hobby: string[]
  code: {
    lang: string
    tool: string
  }
}

提示

  1. 根据传入属性拆分对象类型(使用结构工具类型PickOmit),添加或去除需要的访问符
  2. 合并拆分的两个对象类型

1. 自定义Partial部分可选

ts
/* 指定部分属性可选 */
type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T
> = Partial<Pick<T, K>> & Omit<T, K>

// 让IPerson接口中的code 和 hobby属性可选
type Res = MarkPropsAsOptional<IPerson, 'hobby' | 'code'>
/*
type res = Partial<Pick<IPerson, "hobby" | "code">> & Omit<IPerson, "hobby" | "code">
*/

T 为需要处理的对象类型,而 K 为需要标记为可选的属性。由于此时 K 必须为 T 内部的属性,因此我们将其约束为 keyof T,即对象属性组成的字面量联合类型。同时为了让它能够直接代替掉 Partial,我们为其指定默认值也为 keyof T,这样在不传入第二个泛型参数时,它的表现就和 Partial 一致,即全量的属性可选。而其组成中,Partial<Pick<T, K>> 为需要标记为可选的属性组成的对象子结构,Omit<T, K> 则为不需要处理的部分,使用交叉类型将其组合即可。

鼠标移入Res,类型结果没有展平,可以再实现一个展平类型的工具类型(将类型复制一下就行):

ts
type Flatten<T> = { [k in keyof T]: T[k] }

改造 MarkPropsAsOptional:

ts
type MarkPropsAsOptional<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>> // 最后借助Flatten<T>展平

type Res = MarkPropsAsOptional<IPerson, 'code' | 'hobby'>
/*
type res = {
    hobby?: string[] | undefined;
    code?: {
        lang: string;
        tool: string;
    } | undefined;
    name: string;
    age: number;
}
*/

2. 其他部分修饰

  1. 指定部分属性必须
ts
type MarkPropsAsRequired<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Required<Pick<T, K>>>
  1. 指定部分属性只读
ts
export type MarkPropsAsReadonly<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>
  1. 指定部分属性去除只读Mutable 为自定义类型, 内部实现 :

type Mutable<T> = {-readonly [k in keyof T]:T[k]}

ts
type MarkPropsAsMutable<
  T extends object,
  K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Mutable<Pick<T, K>>>