TS内置工具与类型体操

  • 类型系统其实在很多语言里面都是有的,比如 Java、Swift、C++等等,但是相对来说 TypeScript 的类型非常灵活。
    • 这是因为 TypeScript 的目的是为 JavaScript 添加一套类型校验系统,因为 JavaScript 本身的灵活性,也让 TypeScript 类型系统 不得不增加更附加的功能以适配 JavaScript 的灵活性
    • 所以 TypeScript 是一种可以支持类型编程的类型系统
  • 这种类型编程系统为 TypeScript 增加了很大的灵活度,同时也增加了它的难度
    • 如果你不仅仅在开发业务的时候为自己的 JavaScript 代码增加上类型约束,那么基本不需要太多的类型编程能力
    • 但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程
  • TypeScript 本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于 this 的类型工具)
  • 很多开发者为了进一步增强自己的 TypeScript 编程能力,还会专门去做一些类型体操的题目

条件类型

很多时候,日常开发中需要基于输入的值来决定输出的值,同样也需要基于输入的值的类型来决定输出的值的类型

条件类型(Conditional types)就是用来描述输入类型和输出类型之间的关系

  • 条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ): 条件类型(Conditional Types)
  • SomeType extends OtherType ? TrueType : FalseType;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type IDType = number | string;

// 判断number是否是extendsIDType
// const res = 2 > 3? true: false
type ResType = boolean extends IDType ? true : false; // type ResType = false

// 举个栗子: 函数的重载
// function sum(num1: number, num2: number): number
// function sum(num1: string, num2: string): string

// 错误的做法: 类型扩大化
// function sum(num1: string|number, num2: string|number): string

function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
function sum(num1: any, num2: any) {
return num1 + num2;
}

const res = sum(20, 30);
const res2 = sum("abc", "cba");
// const res3 = sum(123, "cba") //error

条件类型中的推断

在条件类型中推断(Inferring Within Conditional Types)

  • 条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果

比如现在有一个函数类型,想要获取到一个函数的参数类型和返回值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type CalcFnType = (num1: numebr, num2: number) => number

function foo() {
return "abc"
}

// 返回函数的返回值类型 内置工具为 ReturnType
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never
// 返回函数的参数类型
type MyParameterType<T extends (...args; any[]) => any> = T extends (...args: infer P) => any ? P : never

// 获取一个函数的返回值类型: 内置工具
type CalcReturnType = MyReturnType<CalcFnType>
type FooReturnType = MyReturnType<typeof foo>
// type FooReturnType2 = MyReturnType<boolean>

type CalcParameterType = MyParameterType<CalcFnType>

分发条件类型

当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的(distributive)

如果在 ToArray 传入一个联合类型,这个条件类型会被应用到联合类型的每个成员

  • 当传入string | number时,会遍历联合类型中的每一个成员
  • 相当于ToArray | ToArray
  • 所以最后的结果是:string[] | number[]
1
2
3
4
5
6
7
type toArray<T> = T extends any ? T[] : never;
// number[] | string[]

type NumArray = toArray<number>; // type NumArray = number[]

// number[] | string[] 而不是 (number | string)[]
type NumAndStrArray = toArray<number | string>; // type NumAndStrArray = number[] | string[]

内置工具

Partial

用于构造一个 Type 下面的所有属性都设置为可选的类型

格式:Partial<Type>

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IKun {
name: string;
age: number;
slogan?: string;
}

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

// IKun都变成可选的
// type IKunOptional = Partial<IKun>
type IKunOptional = MyPartial<IKun>;

Required

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

格式:Required<Type>

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IKun {
name: string;
age: number;
slogan?: string;
}

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

// IKun都变成可选的
// type IKunRequire = Required<IKun>
type IKunRequire = MyRequired<IKun>;

Readonly

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

格式:Readonly<Type>

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IKun {
name: string;
age: number;
slogan?: string;
}

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

// IKun都变成只读的
// type IKunReadonly = Readonly<IKun>
type IKunReadonly = MyReadonly<IKun>;

Record

用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。

格式:Record<Keys, Type>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface IKun {
name: string;
age: number;
slogan?: string;
}

type Keys = keyof IKun; // type Keys = "name" | "age" | "slogan"
type Res = keyof any; // type Res = number|string|symbol

// 确实keys一定是可以作为key的联合类型
type MyRecord<Keys extends keyof any, T> = {
[P in Keys]: T;
};

// IKun变成遍布各地的
type Citys = "上海" | "北京" | "洛杉矶";
// type IKuns = Record<Citys, IKun>
type IKuns = MyRecord<Citys, IKun>;

const ikuns: IKuns = {
上海: {
name: "xxx",
age: 10
},
北京: {
name: "yyy",
age: 5
},
洛杉矶: {
name: "zzz",
age: 3
}
};

Pick

用于构造一个类型,它是从Type类型里面挑了一些属性Keys

格式:Pick<Type, Keys>

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IKun {
name: string;
age: number;
slogan?: string;
}

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

// 挑选IKun的一些属性
// type IKuns = Pick<IKun, "slogan" | "name">
type IKuns = MyPick<IKun, "slogan" | "name">;

Omit

用于构造一个类型,它是从Type类型里面过滤(排除)一些属性Keys

用法:Omit<Type, Keys>

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IKun {
name: string;
age: number;
slogan?: string;
}

type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};

// 排除IKun的一些属性
// type IKuns = Omit<IKun, "slogan" | "name">
type IKuns = MyOmit<IKun, "slogan" | "name">;

Exclude

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

用法:Exclude<UnionType, ExcludedMembers>

1
2
3
4
5
6
7
type IKun = "sing" | "dance" | "rap";

type MyExclude<T, E> = T extends E ? never : T;

// 排除IKun的一些字面量类型
// type IKuns = Exclude<IKun, "rap">
type IKuns = MyExclude<IKun, "rap">; // type IKuns = "sing" | "dance"

Extract

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

用法:Extract<Type, Union>

1
2
3
4
5
6
7
type IKun = "sing" | "dance" | "rap";

type MyExtract<T, E> = T extends E ? T : never;

// 提取包含IKun的一些字面量类型
// type IKuns = Extract<IKun, "sing" | "dance">
type IKuns = MyExtract<IKun, "sing" | "dance">; // type IKuns = "sing" | "dance"

NonNullable

用于构造一个类型,这个类型从 Type 中排除了所有的nullundefined的类型

用法:NonNullable<T>

1
2
3
4
5
6
7
type IKun = "sing" | "dance" | "rap" | null | undefined;

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

// 排除IKun字面量属性中的null和undefined
// type IKuns = NonNullable<IKun>
type IKuns = MyNonNullable<IKun>; // type IKuns = "sing" | "dance" | "rap"

ReturnType

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

用法:ReturnType<T>

1
2
3
4
5
6
7
8
9
10
11
function iKun() {
return "哎呦,你干嘛!";
}

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

// 获取ikun函数的返回值
// type iKunReturnType = ReturnType<typeof iKun>
type iKunReturnType = MyReturnType<typeof iKun>; // type iKunReturnType = string

InstanceType

用于构造一个由所有Type的构造函数的实例类型组成的类型

用法:InstanceType<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {}
class Dog {}

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

const p1: Person = new Person();

// typeof Person: 构造函数具体的类型
// InstanceType构造函数创建出来的实例对象的类型
type MyPerson = MyInstanceType<typeof Person>; // type MyPerson = Person
const p2: MyPerson = new Person();

// 构造函数的例子
// 通过创建实例的工具函数时会用到这个InstanceType
function factory<T extends new (...args: any[]) => any>(ctor: T): HYInstanceType<T> {
return new ctor();
}

const p3 = factory(Person); // typeof p3 = Person
const d = factory(Dog); // typeof d = Dog

Parameters

用于获取函数的所有参数

用法:Parameters<T>

1
2
3
4
5
6
7
8
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

function test(a: string, b: number) {
return a + b;
}

type ParamsType = Parameters<typeof test>;
// type ParamsType = [a: string, b: number]

自定义工具

ChangeFunctionReturn

用于改变一个函数的返回值类型

用法:ChangeFunctionReturn<F, R>

1
2
3
4
5
6
7
8
9
10
type ChangeFunctionReturn<F extends (...args: any[]) => any, R = any> = (
...args: Parameters<F>
) => R;

function test(a: string, b: number) {
return a + b;
}

type TestFnType = ChangeFunctionReturn<typeof test, Array<string>>;
// type TesFnType = (a: string, b: number) => string[]

ChangeFunctionParameters

用于改变一个函数的参数类型

用法:ChangeFunctionParameters<F, P>

1
2
3
4
5
6
7
8
9
10
11
type ChangeFunctionParameters<F extends (...args: any[]) => any, P extends any[]> = (
...args: P
) => ReturnType<F>;

function test(a: string, b: number) {
return a + b;
}

type ParametersType = [x: string, y: number, z: number];
type TestFnType = ChangeFunctionParameters<typeof test, ParametersType>;
// type TestType = (x: string, y: number, z: number) => string

DeepReadonly

用于构造一个Type下面的所有属性(包括深层Type)全都设置为只读的类型

用法:DeepReadonly<F>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type DeepReadonly<T extends Record<string | symbol, any>> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};

interface IKun {
name: string;
age: number;
hobbies: {
sing: string;
dance: string;
rap: string;
};
}

// IKun都变成只读的, 包括 sing, dance, rap
type ReadonlyIKun = DeepReadonly<IKun>;

DeepRequired

用于构造一个Type下面的所有属性(包括深层Type)全都设置为** 必填的类型**

用法:DeepRequired<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type DeepRequired<T extends object> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};

interface IKun {
name: string;
age?: number;
hobbies: {
sing?: string;
dance?: string;
rap?: string;
};
}

// IKun都变成必填的, 包括 age, sing, dance, rap
type RequiredIKun = DeepRequired<IKun>;

Kebab

将小驼峰转换为短横线 kebab-case

用法:Kebab<T>

1
2
3
4
5
6
type Kebab<T extends string, A extends string = ""> = T extends `${infer F}${infer R}`
? Kebab<R, `${A}${F extends Lowercase<F> ? "" : "-"}${Lowercase<F>}`>
: A;

type Str = "getRandomNumber";
type Result = Kebab<Str>; // "get-random-number"

GetOptional

获取对象的可选属性

用法:GetOptional<T>

1
2
3
4
5
6
7
8
9
10
11
type GetOptional<T extends object> = {
[K in keyof T as T[K] extends Required<T>[K] ? never : K]: T[K];
};

interface IKun {
name: string;
age?: number;
hobbies?: [];
}

type IKunOptional = GetOptional<IKun>; // { age?: number; hobbies?: string[]; }