如何让 TypeScript 识别类中一个字段的值限制另一个字段的类型?
示例代码(playground):
class Cat { purr() {/*...*/} }
class Dog { bark() {/*...*/} }
interface TypeMap {
cat: Cat;
dog: Dog;
}
class Pet<C extends keyof TypeMap> {
commonName: C;
animal: TypeMap[C];
constructor(commonName : C, animal: TypeMap[C]) {
this.commonName = commonName;
/*
//Dropping the parameter and trying to create the object here doesn't work:
if(commonName === 'cat') {
this.animal = new Cat();
} //...
// because Type 'Cat' is not assignable to type 'Cat & Dog'.
*/
this.animal = animal;
}
makeSound() {
if(this.commonName === 'cat') {
//Error: this.animal is of type 'Cat | Dog',
//not narrowed to Cat as hoped.
return this.animal.purr();
} else if (this.commonName === 'dog') {
//Error: this.animal is of type 'Cat | Dog',
//not narrowed to Dog as hoped.
return this.animal.bark();
}
}
}
makeSound()
中显示的限制类型是我想要完成的示例 - 您应该能够在其中检查 commonName 以了解有关 this 类型的更多信息.animal
以缩小其类型的方式。
关于函数参数的相关问题是 here .
最佳答案
Pet
的 commonName
和 animal
属性自然使 Pet
成为 discriminated union其中 commonName
属性是判别式。如果 Pet
是这样一个受歧视的联合,那么您的 makeSound()
实现将进行类型检查而不会出现问题。
唯一的问题是Pet
是a class
,并且类实例类型需要定义为 an interface
,并且接口(interface)不能是 union根本不存在,更不用说是一个受歧视的工会了。
相反,您尝试将 Pet
表示为 generic 。这或多或少捕获了 commonName
和 animal
的约束(好吧,C
本身可以是联合类型,这种情况会破坏一些东西,但对于这种情况,我们将忽略潜在的不健全性。出于我们的目的,我们可以假设C
将是“cat”
或“dog”
或 keyof TypeMap
的任何其他单个成员)。但编译器不允许 makeSound() 进行类型检查。
所以我们必须重构(或者用 type assertions 来抑制错误,但我们不要这样做)。
一个潜在的重构是将 makeSound()
中的检查抽象为查找,这样我们就不会进行逐例控制流。它看起来像这样:
const petSound = {
cat: (cat: Cat) => cat.purr(),
dog: (dog: Dog) => dog.bark()
}
class Pet<C extends keyof TypeMap> {
makeSound() {
petSound[this.commonName](this.animal); // error
}
}
问题在于编译器无法跟踪 petSound[this.commonName]
和 this.animal
类型之间的相关性。这就是我一直称之为“相关联合”的问题,如 microsoft/TypeScript#30581 中所述。 。此问题的修复已在 microsoft/TypeScript#47109 中实现。并涉及为 petSound
提供一个分布式对象类型,编译器可以在其中跟踪相关性。它看起来像这样:
const petSound: { [C in keyof TypeMap]: (animal: TypeMap[C]) => void } =
{
cat: cat => cat.purr(),
dog: dog => dog.bark()
};
只需给 petSound
一个 mapped type迭代 keyof TypeMap
,上面的 makeSound()
编译没有错误:
class Pet<C extends keyof TypeMap> {
makeSound() {
petSound[this.commonName](this.animal); // okay
}
}
看起来不错!
(请注意,这仍然不合理;有人可以将 C
指定为 keyof TypeMap
,然后为给定的对象提供完全错误的 animal
属性commonName
属性。但是 TypeScript 故意忽略这种不合理性,以便使这个相关的联合事物发挥作用。请参阅 microsoft/TypeScript#48730 了解更多信息)。
关于typescript - 如何在 TypeScript 中链接/连接/关联两个类字段的类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72887380/