likes
comments
collection
share

一文搞懂typescript工具类型及其原理

作者站长头像
站长
· 阅读数 44

前言

相信很多人都在学习typescript,这里介绍泛型常见的工具类型函数(Utility Types)及其在typescript中的源码实现。

工具函数

可能有些刚开始学ts的还不清楚什么工具类型指的是什么,工具类型就是TypeScript提供的一些实用类型来实现常见的类型转换,这些类型是全局可用的,所有的类型定义在typescript源码中也都能找到。

Partial

Partial<T>,将传入T类型的所有属性设为可选属性。

例如下面的实例代码,定义一个user接口类型,通过Partial,生成一个所有属性为可选的新的user类型。一个常见的使用场景就是合并或更新对象属性的方法,还有组件写props时也非常好用。

interface User {
    name: string;
    age: num;
}

type UpdateUser = Partial<User>
// {name?: string; age?: number}

原理:首先通过keyof T,遍历出类型T的所有属性,然后通过in操作符进行遍历,最后在属性后加上?,将属性变为可选属性。

// https://github.com/microsoft/TypeScript/blob/HEAD/src/lib/es5.d.ts#L1517
type Partial<T> = {
    [P in keyof T]?: T[P];
}

Required

Required,可以将传入参数的所有属性设为必选属性。 与Partial相反的一个方法,通过Required函数声明的类型属性会全部变为必选,看示例代码。

interface Props {
    a?: number;
    b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 }; // Property 'b' is missing in type '{ a: number; }'

原理:用于将 T 类型的所有属性设置为必选状态,首先通过 keyof T,取出类型 T 的所有属性, 然后通过 in 操作符进行遍历,最后在属性后的 ? 前加上 -,将属性变为必选属性。

这里顺便讲下-这个符号的作用,这是TypeScript 2.8为映射类型增加了添加或删除特定修饰符的能力。具体来说,readonly?映射类型中的属性修饰符现在可以加上+-前缀,以指示应该添加或删除该修饰符,当然一般+号是可以省略的。详情有2.8版本的官方文档里有提到(点击此链接跳转到官网

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

Readonly

Readonly<T>T的所有属性映射为只读的,相当于在所有属性上加了readonly。加上readonly的属性即为只读属性,不可修改。

interface IPerson {
  name: string
  age: number
}

type IReadOnlyPerson = Readonly<IPerson>
/**{
  readonly name: string; 
  readonly age: number;
}*/

原理: 遍历T的所有属性并加上readonly

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

Record

Record<T, K>,接收两个泛型,生成key为K类型,value为T类型的对象类型。

示例代码:

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" },

};

原理:接收两个泛型,Kstring | number | symbol可以继承的类型,这三个也是对象key所支持的基础类型,然后通过 in 操作符对 K 进行遍历,每一个属性的类型为T类型。

type Record<K extends string | number | symbol, T> = {
    [P in K]: T;
}

Pick

Pick<T, K>,将T类型中的K属性提取出来,生成新的类型,类型过滤的意思。类似于与lodash中的pick方法。

示例:定义一个矩形Rectangular类型,包含长宽高属性,而如果是正方形的时候,只需要一个长度的属性,这时就可以用Pick方法提取Rectangular中的length属性生成正方形的类型。

interface Rectangular {
    length: number
    height: number
    width: number
}

type Square = Pick<Rectangular, 'length'>
// { length: number; }

const temp: Square = { length: 5 }

原理:从 T 类型中提取部分属性,作为新的返回类型。

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

Omit

Omit<T, K>,与Pick相反,将T类型中的K属性全部删除,生成新的类型。

示例:同Pick示例,反过来用Omit删除正方形所不需要Rectangular的其他属性,生成正方形的类型。

interface Rectangular {
    length: number
    height: number
    width: number
}

type Square = Omit<Rectangular, 'height' | 'width'>
// 返回类型:
type Square = {
    length: number;
}

const temp: Square = { length: 5 }

原理:结合PickExclude方法,提取出不含K属性的类型。

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

Exclude

Exclude<T, U>,从联合类型T中剔除可以赋值给类型U的部分,直接看示例

示例:

type T0 = Exclude<"a" | "b" | "c", "a">; //  "b" | "c"

type T1 = Exclude<"a" | "b" | "c", "a" | "b">; //  "c"

type T2 = Exclude<string | number | (() => void), Function>;

原理:源码很简单,判断联合类型T是否可以赋值给联合类型U,是则返回T,否则返回never。never是一个特殊的类型,在这里可以表示为空的联合类型,在于与其他类型的联合后,结果为其他类型。

type Exclude<T, U> = T extends U ? never : T;

这里还涉及到extends条件类型的特殊情况,extends的前参T如果是一个泛型参数。对于使用extends关键字的条件类型(即上面的三元表达式类型),如果extends前面的参数是一个泛型类型,当传入该参数的是联合类型,两个类型会成为分配条件类型(Distributive Conditional Types)。分配条件类型是指,将联合类型的联合项拆成单项,分别代入条件类型,然后将每个单项代入得到的结果再联合起来,得到最终的判断结果。

大家可能看了还是一脸懵,啥分配条件类型呀,看我举个🌰:

type P<T> = T extends 'x' ? string : number;
type A = P<'x' | 'y'> // A的类型为string | number

该例中,extends的前参为T,T是一个泛型参数。在A的定义中,给T传入的是联合类型'x' | 'y',满足分配律,将'x'和'y'分别带入P<T>

P<'x' | 'y'> => P<'x'> | P<'y'>

'x'代入得到

'x' extends 'x' ? string : number => string

'y'代入得到

'y' extends 'x' ? string : number => number

然后将每一项代入得到的结果联合起来,得到string | number

总之,满足两个要点即可适用分配律:第一,参数是泛型类型,第二,代入参数的是联合类型

Extract

Extract<T, U>,提取T中可以赋值给U的部分属性,即提取两边相交的属性,也就是并集的概念。

示例:

type T01 = Extract<"a" | "b" | "c" | "d""a" | "c" | "f">;  // "a" | "c"

type T02 = Extract<string | number | (() => void), Function>;  // () => void

原理:与Exclude相反,判断联合类型T是否可以赋值给联合类型U,是则返回never,否则返回T。

type Extract<T, U> = T extends U ? T : never;

NonNullable

NonNullable<T>,过滤类型中的 null 及 undefined 类型。

示例:

type T0 = NonNullable<string | number | undefined>; //  string | number

type T1 = NonNullable<string[] | null | undefined>; // string[]

原理:判断T是否可以赋值给null或者undefined类型,是则返回never,否则返回T,如果这段看不明白的可以再看下Exclude那段关于extends的补充说明。

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

Parameters

Parameters<T>T为函数类型,获取函数的参数类型组成 元组

示例:如果传入的不是函数类型的将会报错

declare function f1(arg: { a: number; b: string }): void;

type T0 = Parameters<() => string>; // []

type T1 = Parameters<(s: string) => void>; // [s:string]

type T2 = Parameters<<T>(arg: T) => T>; //  [arg: unknown]

type T3 = Parameters<any>; // unknown[]

type T3 = Parameters<never>; // never

原理:

  • Parameters首先约束参数T必须是个函数类型
  • 判断T是否是函数类型,如果是则使用infer P暂时存一下函数的参数类型,直接用 P 即可得到这个类型并返回,否则就返回never
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

这里用到了infer,infer P标记一个泛型,表示这个泛型是一个待推断的类型,并且可以直接使用。

ReturnType

ReturnType<T>,获取函数的返回值类型

type T0 = ReturnType<() => string>; // string

type T1 = ReturnType<(s: string) => void>; // void

type T2 = ReturnType<<T>() => T>; // unknown

type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]

原理:与Parameters类似

  • ReturnType首先约束参数T必须是个函数类型
  • 判断T是否是函数类型,如果是则使用infer R暂时存一下函数的返回值类型,后面的语句直接用 R 即可得到这个类型并返回,否则就返回any
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

总结

类型用法
Partial<T>T 中所有的属性都变成可选类型
Required<T>T中所有的属性都变成必选类型
Readonly<T>将T所有属性转为只读
Record<T, K>返回属性名为类型K,值为T的对象类型
Pick<T, K>T类型中的K属性提取出来
Omit<T, K>T类型中的K属性剔除
Exclude<T, U>从联合类型T中剔除可以赋值给类型U的部分
Extract<T, U>提取联合类型T和联合类型U交集部分
NonNullable过滤类型中的 null 及 undefined 类型
Parameters获取函数的参数类型组成 元组
ReturnType获取函数的返回类型

参考链接

最后

文中有些写的不是很准确或者说得不对的,欢迎指出来,十分感谢。

转载自:https://juejin.cn/post/7098122956542312484
评论
请登录