2

一篇学会TypeScript 实用工具类型

 2 years ago
source link: https://www.fly63.com/article/detial/11391
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
更新日期: 2022-04-26阅读量: 3标签: 类型分享

扫一扫分享

工具类型是 Typescript 附带的特殊类型,可用于提高代码的可读性和灵活性。简单地说,根据提供的类型,工具类型将会按照规则构造一个新类型。下面就来看看TypeScript中有哪些常用的工具类型以及使用方式!

1. Partial

Partial 作用是将传入的属性变为可选项。适用于对类型结构不明确的情况。它使用了两个关键字:keyof和in,先来看看它们都是什么含义。keyof 可以用来取得接口的所有 key 值:

type Person = {
  name: string;
  age: number;
  height: number;
}

type T = keyof Person 
// T 类型为: "name" | "age" | "number"

in关键字可以遍历枚举类型,:

type Person = "name" | "age" | "number"

type Obj =  {
  [p in Person]: any
} 
// Obj 的类型为: { name: any, age: any, number: any }

keyof 可以产生联合类型, in 可以遍历枚举类型。所以可以一起使用, 下面是Partial的定义:

/**
 * Make all properties in T optional
 * 将T中的所有属性设置为可选
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

这里,keyof T 用来获取 T 所有属性名, 然后使用 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值。中间的?就用来将属性设置为可选。

来看下面的例子:

type Person = {
  name: string;
  age: number;
  height: number;
}

type PartialPerson = Partial<Person>;
// PartialPerson 的类型为 {name?: string; age?: number; height?: number;}

const person: PartialPerson = {
  name: "zhangsan";
}

这里就使用Partial将Person类型中的属性都指定为可选属性。

2. Required

Required 的作用是将传入的属性变为必选项,和上面的Partial恰好相反,其声明如下:

/**
 * Make all properties in T required
 * 将T中的所有属性设置为必选
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

可以看到,这里使用-?将属性设置为必选,可以理解为减去问号。使用形式和上面的Partial差不多:

type Person = {
  name?: string;
  age?: number;
  height?: number;
}

type RequiredPerson = Required<Person>;
// RequiredPerson 的类型为 {name: string; age: number; height: number;}

const person: RequiredPerson = {
  name: "zhangsan";
  age: 18;
  height: 180;
}

这里就使用Required将Person类型中的属性都指定为必选属性。

3. Readonly

将T类型的所有属性设置为只读(readonly),构造出来类型的属性不能被再次赋值。Readonly的声明形式如下:

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

来看下面的例子:

type Person = {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
  name: "zhangsan",
  age: 18
}

person.age = 20;  //  Error: cannot reassign a readonly property

可以看到,通过 Readonly 将Person的属性转化成了只读,不能再进行赋值操作。Readonly 类型对于冻结对象非常有用。

4. Pick<Type, Keys>

从 Type类型中挑选部分属性 Keys 来构造新的类型。它的声明形式如下:

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

来看下面的例子:

type Person = {
  name: string;
  age: number;
  height: number;
}

const person: Pick<Person, "name" | "age"> = {
  name: "zhangsan",
  age: 18
}

这样就使用Pick从Person类型中挑出来了name和age属性的类型,新的类型中只包含这两个属性。

5. Record<Keys, Type>

Record 用来构造一个类型,其属性名的类型为Keys中的类型,属性值的类型为Type。这个工具类型可用来将某个类型的属性映射到另一个类型上,下面是其声明形式:

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

来看下面的例子:

type Pageinfo = {
    title: string;
}

type Page = 'home' | 'about' | 'contact';

const page: Record<Page, Pageinfo> = {
    about: {title: 'about'},
    contact: {title: 'contact'},
    home: {title: 'home'},
}

6. Exclude<Type, ExcludedUnion>

Exclude 用于从类型Type中去除不在ExcludedUnion类型中的成员,下面是其声明的形式:

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

来看下面的例子:

type Person = {
  name: string;
  age: number;
  height: number;
}

const person: Exclude<Person, "age" | "sex"> = {
  name: "zhangsan";
  height: 180;
}

这里就使用Exclude将Person类型中的age属性给剔除了,只会剔除两个参数中都包含的属性。

7. Extract<Type, Union>

Extract 用于从类型Type中取出可分配给Union类型的成员。作用与Exclude相反。下面是它的声明形式:

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

来看下面的例子:

type ExtractedType = Extract<"x" | "y" | "z", "x" | "y">;
// "x" | "y"

该工具类型对于找出两种类型的公共部分很有用:

interface Human {
  id: string;
  name: string;
  surname: string;
}

interface Cat {
  id: string;
  name: string;
  sound: string;
}

// "id" | "name"
type CommonKeys = Extract<keyof Human, keyof Cat>;

8. Omit<Type, Keys>

上面的 Pick 和 Exclude 都是最基础的工具类型,很多时候用 Pick 或者 Exclude 可能不如直接写类型更直接。而 Omit 就基于这两个来做的一个更抽象的封装,它允许从一个对象中剔除若干个属性,剩下的就是需要的新类型。下面是它的声明形式:

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

来看下面的例子:

type Person = {
  name: string;
  age: number;
  height: number;
}

const person: Omit<Person, "age" | "height"> = {
  name: "zhangsan";
}

这样就使用Omit从Person类型中剔除了 age 和 height 属性,只剩下 name 属性。

9. ReturnType

ReturnType会返回函数返回值的类型,其声明形式如下:

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

来看下面的例子:

function foo(type): boolean {
  return type === 0
}

type FooType = ReturnType<typeof foo>

这里使用 typeof 是为了获取 foo 的函数签名,等价于 (type: any) => boolean。

10. InstanceType

InstanceType 会返回 Type 构造函数类型的实例类型。其声明形式如下:

/**
 * Obtain the return type of a constructor function type
 */
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

来看下面的例子:

class Person {
  name: string;
  age: number;

  constructor(person: { name: string; age: number }) {
    this.name = person.name;
    this.age = person.age;
  }
}

type PersonInstanceType = InstanceType<typeof Person>;
// PersonInstanceType 的类型:{ name: string; age: number }

当然,你可能不会这么写,因为可以直接使用UserManager类型:

class Person {
  name: string;
  age: number;

  constructor(person: { name: string; age: number }) {
    this.name = person.name;
    this.age = person.age;
  }
}

const person: Person = {
  name: "zhangsan",
  age: 18,
};

这就等价于:

class Person {
  name: string;
  age: number;

  constructor(person: { name: string; age: number }) {
    this.name = person.name;
    this.age = person.age;
  }
}

type PersonInstanceType = InstanceType<typeof Person>;
                                       
const person: PersonInstanceType = {
  name: "zhangsan",
  age: 18,
};

当我们在 TypeScript 中创建动态类时,InstanceType可以用于检索动态实例的类型。

11. Parameters

Parameters 可以从函数类型Type的参数中使用的类型构造一个元组类型。其声明形式如下:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

来看下面的例子:

const add = (x: number, y: number) => {
  return x + y;
};

type FunctionParameters = Parameters<typeof add>;
// FunctionParameters 的类型:[x: number, y: number]

除此之外,还可以检测单个参数:

// "number"
type FirstParam = Parameters<typeof add>[0];

// "number"
type SecondParam = Parameters<typeof add>[1];

// "undefined"
type ThirdParam = Parameters<typeof add>[2];

Parameters 对于获取函数参数的类型以确保类型安全很有用,尤其是在使用第三方库时:

const saveUser = (user: { name: string; height: number; age: number }) => {
  // ...
};

const user: Parameters<typeof saveUser>[0] = {
  name: "zhangsan",
  height: 180,
  age: 18,
};

12. ConstructorParameters

ConstructorParameters 可以从构造函数的类型来构造元组或数组类型。其声明形式如下:

/**
 * Obtain the parameters of a constructor function type in a tuple
 */
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

它类似于参数,但适用于类构造函数:

class Person {
  private name: string;
  private age: number;

  constructor(person: { name: string; age: number }) {
    this.name = person.name;
    this.age = person.age;
  }
}

type ConstructorParametersType = ConstructorParameters<typeof Person>;
// ConstructorParametersType 的类型:[person: { name: string, age: number}]

与 Parameters 类型一样,当使用外部库时,它有助于确保构造函数接受我们传入的参数:

class Person {
  private name: string;
  private age: number;

  constructor(person: { name: string; age: number }) {
    this.name = person.name;
    this.age = person.age;
  }
}

const params: ConstructorParameters<typeof Person>[0] = {
  name: "zhangsan",
  age: 18,
};

13. NonNullable

NonNullable 通过从Type中排除null和undefined来创建新类型。它就等价于Exclude。其声明形式如下:

/**
 * Exclude null and undefined from T
 */
type NonNullable<T> = T extends null | undefined ? never : T;

来看下面的例子:

type Type = string | null | undefined; 

// string
type NonNullableType = NonNullable<Type>;

这里就使用NonNullable将Type中的null和undefined剔除掉了。

来源: 前端充电宝

链接: https://www.fly63.com/article/detial/11391


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK