TypeScript 的真正高手如何自如应对 Pick 操作后的 Union 关系?
2024-01-21 03:03:17
大家好,欢迎来到 TypeScript 每周挑战的第二期!本期挑战题目是:如何在 Pick 之后仍然保持 Union 关系?
在 TypeScript 中,Pick 操作符允许我们从一个类型中选择一组特定的属性,创建一个新的类型。例如,我们可以使用 Pick 来从 Person 类型中选择 name 和 age 属性,创建一个新的 PersonInfo 类型:
interface Person {
name: string;
age: number;
city: string;
}
type PersonInfo = Pick<Person, "name" | "age">;
PersonInfo 类型现在只包含 name 和 age 属性,而 city 属性已经被删除了。
现在,假设我们有一个 Union 类型,其中包含 Person 和 PersonInfo 类型:
type PersonOrPersonInfo = Person | PersonInfo;
如果我们尝试使用 Pick 操作符来从 PersonOrPersonInfo 类型中选择 name 和 age 属性,我们会得到一个错误:
type PickedPersonOrPersonInfo = Pick<PersonOrPersonInfo, "name" | "age">;
错误:类型“PersonOrPersonInfo”上不存在属性“name”。
这是因为 TypeScript 不允许我们对 Union 类型使用 Pick 操作符。Union 类型表示一组可能的值,而 Pick 操作符只能从单个类型中选择属性。
那么,如何解决这个问题呢?一种方法是使用 TypeScript 的另一个特性:条件类型。条件类型允许我们根据某个条件来创建一个新的类型。例如,我们可以使用条件类型来创建一个新的类型,该类型只包含 PersonOrPersonInfo 类型中具有 name 和 age 属性的值:
type PickedPersonOrPersonInfo = PersonOrPersonInfo extends Person ? Pick<Person, "name" | "age"> : never;
这个条件类型首先检查 PersonOrPersonInfo 类型是否是 Person 类型。如果是,则创建一个新的类型,该类型只包含 name 和 age 属性。如果不是,则创建一个 never 类型,该类型没有任何值。
现在,我们可以使用 PickedPersonOrPersonInfo 类型来确保我们只选择具有 name 和 age 属性的值:
function getNameAndAge(personOrPersonInfo: PersonOrPersonInfo): PickedPersonOrPersonInfo {
return {
name: personOrPersonInfo.name,
age: personOrPersonInfo.age
};
}
这个函数接受一个 PersonOrPersonInfo 类型的值,并返回一个 PickedPersonOrPersonInfo 类型的值。该函数首先检查 personOrPersonInfo 参数是否是 Person 类型。如果是,则从 personOrPersonInfo 中选择 name 和 age 属性。如果不是,则返回一个 never 类型的值。
通过使用条件类型,我们能够保持 Union 关系,同时仍然能够选择特定的属性。这使得我们可以编写更灵活和可维护的 TypeScript 代码。