管理具有相交类型的数组并缩小其范围的正确方法



在打字稿中使用具有不同类型的数组时,我往往会遇到并非所有类型的属性问题。

对于页面上不同类型的部分,具有不同属性的用户的不同角色等等,我遇到了相同的问题。

下面是一个动物的例子:

例如,如果您有猫、狗和狼的类型:

export type Cat = {
animal: 'CAT';
legs: 4;
}
export type Dog = {
animal: 'DOG',
legs: 4,
requiresWalks: true,
walkDistancePerDayKm: 5
}
export type Wolf = {
animal: 'WOLF',
legs: 4,
requiresWalks: true,
walkDistancePerDayKm: 20
}
type Animal = Cat | Dog | Wolf;

const animals: Animal[] = getAnimals();
animals.forEach(animal => {
// here i want to check if the animal requires a walk
if (animal.requiresWalks) {
// Property 'requiresWalks' does not exist on type 'Animal'. Property 'requiresWalks' does not exist on type 'Cat'.
goForAWalkWith(animal)
}
});
// The type "AnimalThatRequiresWalks" does not exist and i want to know how to implement it
goForAWalkWith(animal: AnimalThatRequiresWalks) {
}

如上所述,属性要求步行不能用于缩小类型。

再想象一下我们有20只动物。我在实现可能扩展动物的类型时遇到困难,例如"AnimalThatRequiresWalks",这些类型可能具有与行走动物相关的多个属性。

将这些类型与类型"AnimalThatRequiresWalks"(具有属性"requiresWalks true"和"walkDistancePerDayKm")连接起来的干净实现是什么,我怎样才能正确地将其缩小到"AnimalThatRequiresWalks"?

您有两个问题:

  1. 你如何检查requiresWalks以查看动物是否需要散步?

  2. 您如何定义goForAWalkWithanimal的类型?

Re #1:在尝试使用它之前,您测试以查看对象是否具有该属性(这是一种特定类型的缩小手册调用in运算符缩小):

animals.forEach(animal => {
if ("requiresWalks" in animal && animal.requiresWalks) {
// −−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
goForAWalkWith(animal)
}
});

Re #2,您可以从Animal联合中提取可通过Extract实用程序类型分配给{requiresWalks: true}的所有类型:

function goForAWalkWith(animal: Extract<Animal, {requiresWalks: true}>) {
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ...
}

Extract<Animal, {requiresWalks: true}>Dog | Wolf.

游乐场链接

如果您不想这样做,则不必内联执行此操作,您可以为其定义类型别名,然后使用该别名:

type AnimalThatRequiresWalks = Extract<Animal, {requiresWalks: true}>;
// ...
function goForAWalkWith(animal: AnimalThatRequiresWalks) {
// ...
}

游乐场链接


在注释中,您说过要用显式类型一起定义所有AnimalThatRequiresWalks属性,而不是从DogCat等推断该类型。你可以这样做:

interface AnimalThatRequiresWalks {
animal: string;
requiresWalks: true;
preferredWalkTerrain: "hills" | "paths" | "woods";
walkDistancePerDayKm: number;
}
export type Cat = {
animal: "CAT";
legs: 4;
};
export type Dog = AnimalThatRequiresWalks & {
animal: "DOG";
legs: 4;
walkDistancePerDayKm: 5;        // Only needed if you want to refine its type
preferredWalkTerrain: "paths";  // Same
};
export type Wolf = AnimalThatRequiresWalks & {
animal: "WOLF";
legs: 4;
walkDistancePerDayKm: 20;       // Only needed if you want to refine its type
preferredWalkTerrain: "woods";  // Same
}
type Animal = Cat | Dog | Wolf;
declare const animals: Animal[]; // = getAnimals();
animals.forEach(animal => {
if ("requiresWalks" in animal && animal.requiresWalks) {
goForAWalkWith(animal)
}
});
function goForAWalkWith(animal: AnimalThatRequiresWalks) {
console.log("Go for a walk with the " + animal.animal);
}

游乐场链接

您可能想尝试一下,看看开发人员的体验是什么样的。在定义DogWolf等时,很容易忘记把AnimalThatRequiresWalks &部分放在上面,AnimalThatRequiresWalks["animal"]的类型是string而当你推断它是更窄的"Cat" | "Dog" | "Wolf"。但它可能更方便。

碰巧的是,解决一开始容易忘记AnimalThatRequiresWalks &的一种方法是对动物使用泛型类型:

interface AnimalThatRequiresWalks {
animal: string;
requiresWalks: true;
preferredWalkTerrain: "hills" | "paths" | "woods";
walkDistancePerDayKm: number;
}
type AnimalType<R extends boolean, Type extends object> =
R extends true
? AnimalThatRequiresWalks & Type
: Type;
export type Cat = AnimalType<false, {
animal: "CAT";
legs: 4;
}>;
export type Dog = AnimalType<true, {
animal: "DOG";
legs: 4;
walkDistancePerDayKm: 5;        // Only needed if you want to refine its type
preferredWalkTerrain: "paths";  // Same
}>;
export type Wolf = AnimalType<true, {
animal: "WOLF";
legs: 4;
walkDistancePerDayKm: 20;       // Only needed if you want to refine its type
preferredWalkTerrain: "woods";  // Same
}>;
type Animal = Cat | Dog | Wolf;
declare const animals: Animal[]; // = getAnimals();
animals.forEach(animal => {
if ("requiresWalks" in animal && animal.requiresWalks) {
goForAWalkWith(animal)
}
});
function goForAWalkWith(animal: AnimalThatRequiresWalks) {
console.log("Go for a walk with the " + animal.animal);
}

游乐场链接

也许这样更好,或者它被过度设计了。 :-D

最新更新