TS系列——内置类型

1. 前言

前两篇文章中梳理了一下TypeScript中的基础类型与高级类型,除此之外呢,TypeScript还内置了很多的工具类型。接下来我们来一起看看他们的作用到底是什么呢?

image.png

2. 属性修饰工具类型

属性修饰工具类型,其实就是对属性的一个修饰,包括对象属性和数组元素的可选、必选、只读、可写等。它包含了PartialRequiredReadonly三个工具类型,这几个类型主要使用了属性修饰映射类型索引类型

Partial

将所有属性变为可选属性,也就是为每个属性都添加上?

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

Required

将所有属性变为必选属性,它与Partial刚好相反:

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

-?的含义就是移除可选属性。

Readonly

将所有属性变为只读属性,它会为类型对象的每一项都加上前缀readonly

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

在TypeScript的内置类型中,Readonly并没有与其相反的类型。但是我们可以自己来实现一个工具类型,将属性中的readonly修饰移除:

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

3. 结构工具类型

结构工具类型可以分为结构声明结构处理。它包含了RecordPickOmit三个类型,其中Record属于结构声明,Pick和Omit属于结构处理,它们主要使用条件类型映射类型以及索引类型

Record

将K的每一个属性都定义为T类型

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

其中,K extends keyof any即为键的类型,这里使用extends keyof any标明,传入的K可以是单个类型,也可以是联合类型,而T则为属性的类型。

// 键名均为字符串,键值类型未知
type Record1 = Record<string, unknown>;

// 键名均为字符串,键值类型为任意类型
type Record2 = Record<string, any>;

// 键名均为字符串,键值类型为string
type Record3 = Record<string, string>;


interface IPersonInfo {
    name: string;
    age: number;
}

type IdentityName = 'student''teacher';

const students: Record<IdentityName, IPersonInfo> = {
    student: { name: 'Yancy', age: 26 },
    teacher: { name: 'Jeeny', age: 30 },
};

在有的工具函数库中,还有类似的一些结构声明工具类型实现:

type Dictionary<T> = { 
    [index: string]: T; 
}; 

type NumericDictionary<T> = { 
    [index: number]: T; 
};

Pick

将某个类型的子属性筛选出来,组合成一个新的类型

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

它接受两个泛型参数,T是我们进行结构处理的原类型,一般来说都是对象类型,而K则被约束为T类型的键名联合类型。我们在上一篇文章中提到过,in关键字类型于数组的map方法。

interface IPerson = {
    name: string;
    age: number;
    sex: string;
}

type NameAndAge = Pick<IPerson, 'name' | 'age'>;

// NameAndAge相当于定义了一下类型
interface NameAndAge {
    name: string;
    age: number;
}

Omit

将传入的键从类型中移除。它与Pick相反,Pick是保留传入的键,而Omit则是移除传入的键。

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

我们可以看出,Omit是基于Pick来实现的,只是在传入键的地方做了一个反向操作。Exclude<keyof T, K>就是在T的键名联合类型中剔除掉K的部分。

interface IPerson {
    name: string;
    age: number;
    sex: string;
    isStudent: boolean;
}

type IPersonInfo = Omit<IPseron, 'isStudent'>;

type IPersonStatus = Omit<IPerson, 'name' | 'age' | 'sex'>;

4. 集合工具类型

集合工具类型,故名思义,它是用于对多个类型取并集、交集等,它包含了ExtractExcludeNonNullable。在开始之前呢,我们先回顾一下数学中的一些集合概念:

  • 并集:给定两个集合A,B,把他们所有的元素合并在一起组成的集合,叫做集合A与集合B的并集;
  • 交集:给定两个集合A,B,由所有属于集合A且属于集合B的元素所组成的集合,叫做集合A与集合B的交集
  • 差集:给定两个集合A,B,则所有属于A且不属于B的元素构成的集合,叫做集合A减集合B(或集合A与集合B之差),类似地,对于集合A、B,把集合{x∣x∈A,且x∉B}叫做A与B的差集。
  • 补集:补集一般指绝对补集,即一般地,设S是一个集合,A是S的一个子集,由S中所有不属于A的元素组成的集合,叫做子集A在S中的绝对补集。

Extract

Extract,意为提取,提炼。对应数学中的交集

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

提取T中可赋值给U的所有联合成员来组合成一个新的类型

type T0 = Extract<'a' | 'b' | 'c', 'a' | 'd'>;
// 等同于 type T0 = 'a';

type T1 = Extract<string | number | (() => void), Function>;
// 等同于type T1 = () => void

Exclude

将某个子类型中的子属性剔除。它和Extract刚好相反,对应数学中的差集

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

T中剔除所有可分配给U的联合成员来组合成一个新的类型。

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

type T1 = Exclude<string | number | (() => void), Function>;
// 等同于 type T1 = string | number

NonNullable

排除类型中的null和undefined来构造一个新的类型:

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

T中将类型为null或者是undefined的属性全部剔除:

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

5. 模式匹配工具类型

模式匹配工具类型,包含了ParametersReturnTypeConstructorParametersInstanceType,它们主要使用条件类型infer关键字,前两个是针对于函数类型签名的模式匹配,后两个是针对Class进行的模式匹配。

Parameters

用于提取函数参数的类型

// 函数的通用类型签名
type FunctionType = (...args: any) => any;

type Parameters<T extends FunctionType> = T extends (...args: infer P) => any ? P : never;
type T0 = Parameters<() => string>; // type T0 = []
type T1 = Parameters<(s: string) => void>; // type T1 = [s: string]
type T2 = Parameters<any>; // type T2 = unknown[]

我们还可以在此基础上进一步扩展,例如只匹配第一个参数的类型:

type FirstParameters<T extends FunctionType> = T extends (
    arg: infer P,
    ...args: any
) => any
? P
: never;

type FuncFoo = (arg: number) => void; 
type FuncBar = (...args: string[]) => void; 

type FooFirstParameter = FirstParameter<FuncFoo>; // number 
type BarFirstParameter = FirstParameter<FuncBar>; // string

ReturnType

用于提取函数返回值的类型

type ReturnType<T extends FunctionType> = T extends (...args: any) => infer R ? R : any;
```typescript
type T0 = ReturnType<() => string>; // type T0 = string
type T1 = ReturnType<(s: string) => void>; // type T1 = void

ConstructorParameters

用于获取类的构造函数的参数类型,存储到一个元组中

// Class的通用类型签名
type ClassType = abstract new (...args: any) => any;

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

InstanceType

用于构造由构造函数的实例组成的类型

type InstanceType<T extends ClassType> = T extends abstract new (
  ...args: any
) => infer R
  ? R
  : any;

6. 结语

以上已经囊括了TypeScript中的大部分内置类型,还有一部分后面再补充吧~跟着敲了一遍,对于之前一些不太理解的类型也更加清晰了,留此记录,方便后期查阅,希望也能够为其他前端er提供一些帮助。我是一只小菜鸟,不足之处,还请大家多多指教。

7. 参考资料

  1. TypeScript全面进阶指南
  2. TypeScript官方文档