在3.5版本中,TypeScript在作为TypeScript编译器一部分的lib.es5.d.ts类型定义文件中添加了一个Omit<T, K> 辅助类型。Omit<T, K> 类型让我们创建一个对象类型,省略另一个对象类型的特定属性。
type User = {
id: string;
name: string;
email: string;
};
type UserWithoutEmail = Omit<User, "email">;
// This is equivalent to:
type UserWithoutEmail = {
id: string;
name: string;
};
Omit<T, K> 辅助类型在lib.es5.d.ts中是这样定义的。
/**
* 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> 帮助器类型的。
defining theOmit<T, K> Helper Type
让我们从上面看到的相同的User 类型开始。
type User = {
id: string;
name: string;
email: string;
};
首先,我们需要能够检索到User 类型的所有键。我们可以使用keyof 操作符来检索一个包含该对象类型的所有属性键的字符串字面类型的联盟。
type UserKeys = keyof User;
// This is equivalent to:
type UserKeys = "id" | "name" | "email";
接下来,我们需要能够从字符串字面类型的联盟中排除一个特定的字符串字面类型。在我们的User 类型中,我们希望将"email" 类型从联盟"id" | "name" | "email" 中排除。我们可以使用Exclude<T, U> 辅助类型来完成这个任务。
type UserKeysWithoutEmail = Exclude<UserKeys, "email">;
// This is equivalent to:
type UserKeysWithoutEmail = Exclude<"id" | "name" | "email", "email">;
// This is equivalent to:
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>;
// This is equivalent to:
type UserWithoutEmail = Pick<User, "id" | "name">;
// This is equivalent to:
type UserWithoutEmail = {
id: string;
name: string;
};
下面是在lib.es5.d.ts中如何定义Pick<T, K> 辅助类型的。
/**
* 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] 来检索对象类型P 中的属性类型T 。
现在,让我们在一个单一的类型中总结一下我们使用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">;
由于对象的键只能是字符串、数字或符号,我们可以给我们的Omit<T, K> 辅助类型的类型参数K 添加一个通用约束,只允许键的类型为string,number, 或symbol 。
type Omit<T, K extends string | number | symbol> = Pick<T, Exclude<keyof T, K>>;
通用约束extends string | number | symbol 是一个有点冗长的约束。我们可以用keyof any 类型代替string | number | symbol 联合类型,因为这两个类型是等价的。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
我们就这样了!我们已经得到了Omit<T, K> 辅助类型的确切定义,它可以在lib.es5.d.ts类型定义文件中找到。
/**
* 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>>;
Unrolling Omit<User, "email">
下面是对Omit<User, "email"> 类型的一步步评估。试着跟随每一个步骤来理解TypeScript是如何计算最终类型的。
type User = {
id: string;
name: string;
email: string;
};
type UserWithoutEmail = Omit<User, "email">;
// This is equivalent to:
type UserWithoutEmail = Pick<User, Exclude<keyof User, "email">>;
// This is equivalent to:
type UserWithoutEmail = Pick<User, Exclude<"id" | "name" | "email", "email">>;
// This is equivalent to:
type UserWithoutEmail = Pick<
User,
| ("id" extends "email" ? never : "id")
| ("name" extends "email" ? never : "name")
| ("email" extends "email" ? never : "email")
>;
// This is equivalent to:
type UserWithoutEmail = Pick<User, "id" | "name" | never>;
// This is equivalent to:
type UserWithoutEmail = Pick<User, "id" | "name">;
// This is equivalent to:
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;
};
Et voilà,我们最终的UserWithoutEmail 类型。