typescript - 如何在 TypeScript 中链接/连接/关联两个类字段的类型?

标签 typescript class generics narrowing type-narrowing

如何让 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 .

最佳答案

PetcommonNameanimal 属性自然使 Pet 成为 discriminated union其中 commonName 属性是判别式。如果 Pet 是这样一个受歧视的联合,那么您的 makeSound() 实现将进行类型检查而不会出现问题。

唯一的问题是Peta class ,并且类实例类型需要定义为 an interface ,并且接口(interface)不能是 union根本不存在,更不用说是一个受歧视的工会了。

相反,您尝试将 Pet 表示为 generic 。这或多或少捕获了 commonNameanimal 的约束(好吧,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 了解更多信息)。

Playground link to code

关于typescript - 如何在 TypeScript 中链接/连接/关联两个类字段的类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72887380/

相关文章:

python - 如何检查 Python 3 的列表中是否存在类的实例?

c# - T Get<T>(int id) 和 T Get(int id) 的区别

c# - C#/.Net 泛型如何知道它们的参数类型?

angular - angular2 如何检测有状态管道的变化?

typescript - Resharper 将我的 Promise 标记为错误

Php:单例VS全静态类?什么时候用什么?

java - .class == .class 对比 .class.toString() 对比 .class.toString()

c# - 是否可以创建一个实例并转换回通用接口(interface)类型?

typescript - 如何使用@ngrx/effects 对 Observable 等待产生效果

typescript - Chart.js ionic 2 Angular2 : Background color of bar graph not changing