3.5 版本之后,TypeScript 加了一个 Omit<T, K> 类型。
Omit<T, K> 类型可以从继承的对象属性中剔除某些属性。
type User = {
id: string;
name: string;
email: string;
};
type UserWithoutEmail = Omit<User, "email">;
// 等价于
type UserWithoutEmail = {
id: string;
name: string;
};
Omit<T, K> 在 lib.es5.d.ts 文件里面是这样定义的:
构造一个除了类型 K 之外的具有 T 属性的类型
/**
* 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>>;
把这个类型定义解释清楚并且明白其中的原理,我们可以试着实现一个自己的版本,来还原它的功能。
定义 Omit<T, K> 帮助类型
type User = {
id: string;
name: string;
email: string;
};
首先,我们需要遍历 User 类型中的属性名。我们可以用 keyof
操作符来获取一个包含所有对象属性的字符串集合:
type UserKeys = keyof User;
// 等价于
type UserKeys = "id" | "name" | "email";
然后需要将字符串集合中一些属性取出。
在 User 类型中,需要从 "id" | "name" | "email"
中去掉 "email"
。可以使用 Exclude<T, U>
来做这件事:
type UserKeysWithoutEmail = Exclude<UserKeys, "email">;
// 等价于
type UserKeysWithoutEmail = Exclude<"id" | "name" | "email", "email">;
// 等价于
type UserKeysWithoutEmail = "id" | "name";
Exclude<T, U>
在 lib.es5.d.ts 文件里面是这样定义的:
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
它用了一个条件类型
和 never 类型
。用Exclude<T, U>
可以从集合"id" | "name" | "email"
中去掉那些匹配 "email"
类型的类型。而匹配 "email"
类型的只有它自己,所以就剩下了"id" | "name"
。
最后,我们需要创建一个包含 User 类型属性子集的对象类型。具体地说,就是创建一个只包含 UserKeysWithoutEmail
类型的对象,可以用 Pick<T, K>
来挑出User
类型中对应的属性名。
type UserWithoutEmail = Pick<User, UserKeysWithoutEmail>;
// 等价于
type UserWithoutEmail = Pick<User, "id" | "name">;
// 等价于
type UserWithoutEmail = {
id: string;
name: string;
};
Pick<T, K>
在 lib.es5.d.ts 文件里面是这样定义的
/**
* 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];
};
Pick<T, K>
是一个映射类型
,它用了keyof 操作符
和一个索引类型 T[P]
来获取类型对象类型 T 中的属性 P 。
现在,我们来把上面提到的 keyof
,Exclude<T, U>
和 Pick<T, K>
整合成一个类型
type UserWithoutEmail = Pick<User, Exclude<keyof User, "email">>;
值得注意的是这样的写法只能应用到我们定义的 User
类型中。加入一个范型就能让它用在其他地方了
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
现在,可以计算出 UserWithoutEmail 类型
type UserWithoutEmail = Omit<User, "email">;
因为对象的键只能是字符串、数字或 Symbol,那么我们可以给 K 加个约束条件
type Omit<T, K extends string | number | symbol> = Pick<T, Exclude<keyof T, K>>;
这样直接约束 extends string | number | symbol
看上去有点啰嗦了。我们可以用 keyof any
来实现,因为它们是等价的
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
这样就实现了 lib.es5.d.ts 中定义的Omit<T, K>
类型
/**
* 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>>;
拆解 Omit<User, “email”>
下面这段代码就是逐步拆解的 Omit<User, “email”> 类型。试着跟随每个步骤并理解 TypeScript 是如何计算出最终的类型的
type User = {
id: string;
name: string;
email: string;
};
type UserWithoutEmail = Omit<User, "email">;
// 等价于
type UserWithoutEmail = Pick<User, Exclude<keyof User, "email">>;
// 等价于
type UserWithoutEmail = Pick<User, Exclude<"id" | "name" | "email", "email">>;
// 等价于
type UserWithoutEmail = Pick<
User,
| ("id" extends "email" ? never : "id")
| ("name" extends "email" ? never : "name")
| ("email" extends "email" ? never : "email")
>;
// 等价于
type UserWithoutEmail = Pick<User, "id" | "name" | never>;
// 等价于
type UserWithoutEmail = Pick<User, "id" | "name">;
// 等价于
type UserWithoutEmail = {
[P in "id" | "name"]: User[P];
};
// This is equivalent to:
type UserWithoutEmail = {
id: User["id"];
name: User["name"];
};
// This is equivalent to:
type UserWithoutEmail = {
id: string;
name: string;
};
参考链接
The Omit Helper Type in TypeScript
TypeScript 中的 Omit 帮助类型[中译]