Typescript中的工具类型
前言 🔗
写写那些Typescript
自带的工具类型吧,可能原理方面偏少,更多的是翻译
正文 🔗
Typescript
自带的工具类型,有些在源码中也经常的使用到,比如Record
在Vue@next
中就有用到
为了在IDE
中使用Typescript
,本文使用全局安装方式(yarn global add typescript
,npm
为npm install -g typescript
)
然后在IDEA
的Typescript
配置安装路径
IDEA 2020.2
默认有内置的Typescript
,版本为3.9.5
,不过现在是4.0.5
了
Partial<T>
🔗
Constructs a type with all properties of 'T' set to optional.
This utility will return a type that represents all subsets of a given type.
构造一个传入类型T
的所有属性都为可选的类型
简单地讲就是使得泛型T
的所有属性变为可选的
interface User {
id: number;
name: string;
}
// id和name都变成可选的了
type UserPartial = Partial<User>;
function fn(userPartial: UserPartial) {}
fn({}); // 可以
fn({ id: 1 }); // 可以
fn({ name: "lwf" }); // 可以
可以看下它的源码
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
使用keyof
来获取全部的属性名,然后使用in
来遍历,使用?
使得属性变为可配置,右侧的值为T[P]
对应每个属性对应的类型
Readonly<T>
🔗
Constructs a type with all properties of 'T' set to readonly,
meaning the properties of the constructed type cannot be reassigned.
构造一个传入类型T的所有属性都为只读的类型,这意味着构造出来的类型的属性无法被重新赋值
interface User {
id: number;
name: string;
}
type UserReadOnly = Readonly<User>
const user: UserReadOnly = {
id: 1,
name: "lwf"
};
user.id = 1; // 报错
user.name = "fwl" // 报错
注意这里的只读只对这个对象的属性生效,嵌套的属性不生效
interface User {
id: number;
name: string;
info: {
age: number;
sex: "male" | "female";
}
}
type UserReadOnly = Readonly<User>
const user: UserReadOnly = {
id: 1,
name: "lwf",
info: {
age: 22,
sex: "male"
}
};
user.info = {
age: 11,
sex: "female"
}; // 报错
user.info.age = 11; // 可以
源码如下
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
依然使用keyof
来获取全部的属性名,然后使用in
来遍历,右侧值为对应的类型
然后对每个属性前都用readonly
来标注,使之成为只读的
如果想让一个数组变为只读,可以使用Readonly<Array<T>>
,比如
const arr: Readonly<Array<any>> = [];
arr[1] = 1; // 报错
其实Typescript
已经内置了只读的数组类型ReadonlyArray<T>
const arr: ReadonlyArray<number> = [];
arr[1] = 1; // 报错
对于ReadonlyArray
和Array
的区别就是
ReadonlyArray
在整数属性和length
上使用了readonly
来修饰,而Array
没有
并且ReadonlyArray
少了那些会使得数组发送改变的方法,比如push
,pop
,shift
,unshift
,如下(省略了注释以及相同的API
)
interface Array<T> {
length: number;
pop(): T | undefined;
push(...items: T[]): number;
reverse(): T[];
shift(): T | undefined;
sort(compareFn?: (a: T, b: T) => number): this;
splice(start: number, deleteCount?: number): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[];
unshift(...items: T[]): number;
[n: number]: T;
// 其他API...
}
interface ReadonlyArray<T> {
readonly length: number;
readonly [n: number]: T;
// 其他API...
}
sort
和reverse
由于会交换数组中元素的位置(交换步骤会涉及到赋值),所以没有在ReadonlyArray
中
Record<K, T>
🔗
Constructs a type with a set of properties 'K' of type 'T'.
This utility can be used to map the properties of a type to another type.
构造一个属性为K
的集合,属性对应的类型为T
的类型 这个工具类型可以把一个类型的属性映射到另一个类型
let record: Record<string, Function>;
for (let i = 0; i < 3; i++) {
record[i] = function () {
return i + 1;
}; // 可以
}
for (let i = 0; i < 3; i++) {
record[i] = i + 1; // 报错
}
源码如下
type Record<K extends keyof any, T> = {
[P in K]: T;
};
这里可能会疑惑keyof any
是个什么东西,可以试着给某个变量赋予看一下
发现变量o
类型为number
或者symbol
或者number
也就是泛型K
必须是这三个类型中的其中一种,如果使用Function
作为范型K
,是无效的
let record: Record<Function, string>; // 报错
一般都是使用Record<string, T>
来生成一个类型为T
的映射对象
Pick<T, K>
🔗
Constructs a type by picking the set of properties 'K' from 'T'.
构造一个从T
中选取K
属性集的类型
interface User {
id: number;
name: string;
}
// 现在UserPick类型只有id属性了
type UserPick = Pick<User, "id">;
let user: UserPick = {
id: 1
}; // 可以
user.name = "lwf"; // 报错
源码如下
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
keyof T
获取T
的属性集,依然使用in
来遍历属性集,每个属性对应类型为T[P]
这里可以看出K
必须是T
属性集的一个子集,不是的话会报错
interface User {
id: number;
name: string;
}
// age没有在User中,报错
type UserPick = Pick<User, "id" | "age">;
Omit<T, K>
🔗
Constructs a type by picking all properties from 'T' and then removing 'K'.
构造一个从T
中移除K
属性集的类型
interface User {
id: number;
name: string;
}
// id被移除了,现在只有name了
type UserOmit = Omit<User, "id">;
let user: UserOmit = {
name: "lwf"
}; // 可以
user.id = 1; // 报错
源码如下
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
可以看到,实现依赖了Pick
和Exclude
,可以先在左边点击Exclude
查看
Exclude<keyof T, K>
提取T
中不包含K
的部分,这时得到的只是属性名的集合类型,而不是属性名对应属性值类型
再使用Pick<T, Exclude<keyof T, K>>
把这部分提取出来,这时就变成属性名对应属性值类型
Exclude<T, E>
🔗
Constructs a type by excluding from 'T' all union members that are assignable to 'E'.
构造一个从T
中排除所有可以分配给E
的联合成员的类型
type t1 = Exclude<"a" | "b" | "c", "c">; // "a" | "b"
type t2 = Exclude<string | number, 1 | "" | false>; // string | number
type t3 = Exclude<1 | "" | false, string | number>; // false
类型t1
应该很好理解,有趣的应该是t2
和t3
t2
由于number
无法分给1
,以及string
无法分给空字符串,所以还是string | number
而t3
和t2
相反,1
可以分给number
,以及空字符串可以分给string
,所以t3
类型为false
Extract<T, U>
🔗
Constructs a type by extracting from 'T' all union members that are assignable to 'U'.
构造一个从T
中提取所有可以分配给E
的联合成员的类型
这个和Exclude<T, E>
相反,大白话就是求交集,但是有单向的关系
type t1 = Extract<"a" | "b" | "c", "c">; // "c"
type t2 = Extract<string | number, 1 | "" | false>; // never
type t3 = Extract<1 | "" | false, string | number>; // 1 | ""
t1
还是很好理解,重点还是t2
和t3
t2
由于string无法分给空字符串,以及number
无法分给1
,导致"并集"为空,所以为never
t3
由于1
可以分给number
,以及空字符串可以分给string
,所以并集就为1 | ""
源码如下
type Extract<T, U> = T extends U ? T : never;
NonNullable<T>
🔗
Constructs a type by excluding null and undefined from 'T'.
构造一个从T
中排除null
和undefined
的类型
type t = NonNullable<number | string | null | undefined>; // number | string
源码如下
type NonNullable<T> = T extends null | undefined ? never : T;
Parameters<T>
🔗
Constructs a tuple type from the types used in the parameters of a function type 'T'.
构造一个类型T
函数的参数类型的元素类型
通俗点讲就是提取函数的参数,用数组装起来
type f1 = (id: number, name: string) => void;
type args = Parameters<f1>; // [ id: number, name: string ]
源码如下
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
ConstructorParameters<T>
🔗
Constructs a tuple or array type from the types of a constructor function type.
It produces a tuple type with all the parameter types (or the type never if Type is not a function).
构造一个从构造函数的类型的元组或者数组类型,产生了一个所有参数类型的元组类型(或者当T
不是一个函数时为never
类型)
简单点讲就是提取构造函数的参数类型
interface User {
new(id: number, name: string): void
}
type UserParameters = ConstructorParameters<User>; // [ id: number, name: string ]
源码如下
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
这里和Parameters
的区别就是ConstructorParameters
必须为构造函数,而Parameters
必须为普通函数,两着无法相互转换
ReturnType<T>
🔗
Constructs a type consisting of the return type of function 'T'.
构造一个由T
函数类型的返回类型组成的类型
简单点讲就是提取函数返回的类型
interface User {
id: number;
name: string;
}
interface UserService {
getUser(id: User["id"]): User;
}
type getUserReturnType = ReturnType<UserService["getUser"]>; // User
源码如下
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
InstanceType<T>
🔗
Constructs a type consisting of the instance type of a constructor function in 'T'.
构造一个T
构造函数的实例类型的类型
简单点讲就是返回某个构造函数产生的实例的类型
interface User {
new(id: number, name: string): string;
}
type a = InstanceType<User> // string
源码如下
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
Required<T>
🔗
Constructs a type consisting of all properties of 'T' set to required.
The opposite ofPartial
.
构造一个T类型的所有属性集都是必须的类型 是Partial
操作的相反操作
interface User {
id: number;
name?: string;
}
type UserRequired = Required<User>;
let user: UserRequired = {
id: 1
}; // 报错,提示缺失name
源码如下
type Required<T> = {
[P in keyof T]-?: T[P];
};
ThisParameterType<T>
🔗
Extracts the type of the this parameter for a function type,
or unknown if the function type has no this parameter.
提取一个函数类型的this
参数类型, 如果函数类型没有this
参数,那么为unknown
type Context = {
id: number;
name: string;
}
type f1 = (this: Context) => void;
type t = ThisParameterType<f1>; // Context
源码如下
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
OmitThisParameter<T>
🔗
Removes the this parameter from
T
. IfT
has no explicitly declared this parameter, the result is simplyT
.
Otherwise, a new function type with no this parameter is created fromT
.
Generics are erased and only the last overload signature is propagated into the new function type.
删除T
的this
参数,如果T
没有明确的定义this
参数,结果为简单的T
否则,从T
生成一个没有this
参数的新的函数类型 泛型会被擦除,只有最后的重载签名会被传递到新的函数类型中。
type Context = {
id: number;
name: string;
}
type f1 = (this: Context, age: number) => void;
type t = OmitThisParameter<f1>; // (age: number) => void
源码如下
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
ThisType<T>
🔗
This utility does not return a transformed type. Instead, it serves as a marker for a contextual this type.
Note that the --noImplicitThis flag must be enabled to use this utility.
这个工具不会返回一个转换的类型,而是用于标记一个this
的上下文的类型 使用--noImplicitThis
标志才能够使用这个工具
这个有点混入的意思在里面,这里借用官方的例子,记得在tsconfig.json
中配置
{
"compilerOptions": {
"noImplicitThis": true
}
}
例子
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx;
this.y += dy;
// 如果没有加ThisType<D & M>,这里点不到x和y
},
},
});
源码如下
interface ThisType<T> { }
后记 🔗
重装了下npm
和yarn
,明明把安装路径放D
盘了,还是装到C
盘…
看起来还是非常不错的工具类,不过实现还是有些看不懂,比如泛型的?:
加上extends
,得找个时间好好看看…
以及一些有趣的语法,比如infer
,readonly
,-?
等