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

2024-04-01

如何让 TypeScript 识别类中一个字段的值限制另一个字段的类型?

示例代码(操场 https://www.typescriptlang.org/play?#code/MYGwhgzhAEDCYBdoG9oAcCuAnLAKAlCgPQBUAdBSUQL7TUBQokMAIgPYDmK0ARmFgGsCxcpRp16ASwB2CAKZYAZmGBzoAFQCeaOQFkwaFPQCQwRAC44iANwmAJp0vsOthkyjQACnIQAeWNByAB7y0nYwAnKabIoa2noGAHxGpmwAtmls0gByYGlylrC2xmDSkmlgIJZaOvpoANqwALrFwFkQCFgYwAhseG0ZWbn50IUANNCl5ZXV8XWNTYTIJsYIABaSEGQDmTl5agC80DtD+8XGpCtERCxYbGhoMlzramj8+-JYk2HQnZpPvzYxywckQahe0DYPAAVnIetA1go1A45BBpAByJAAdz6AnMK0kilwJz2IwO5Og6LMCHRSxWqw2WymFRA0CO0jkWKsCAI51o1woZCuRF4cLAGAgahqanR8Bp0E20GkbCQzEkHGkYB4IHBQIQ8UpcugADJoM50ULjMYqCt1psyMzKmzvtMQMUGMYKpEAMpsDBhYTLK2E3B2rYk4aHClUxC0lJW64AURwfUsYYdZRZCpgMV+BtliGgAB8zZx0RNlUhNSmsXI7IDuZMYGt7nXLVaQQhsNJfoyM66yJgcLyVrQ5CBJQrYqG+xH9mzow4OHGgwmiMm7lg033HazFbn9TpDYWS+aKyqlfw7rX671S1xIAjW3Z28ZO93e-bd2Q+IIR1aPQYBggA):

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 https://stackoverflow.com/q/72890232/798371.


The commonName and animal的属性Pet自然地使Pet a 受歧视联盟 https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions哪里的commonName属性是判别式。您的实施makeSound()会毫无问题地输入检查,如果Pet是一个如此受歧视的工会。

唯一的问题是Pet is a class https://www.typescriptlang.org/docs/handbook/2/classes.html,并且类实例类型需要定义为an interface https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces,并且接口不能是union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types根本没有,更不用说是一个受歧视的工会了。

相反,你试图代表Pet as generic https://www.typescriptlang.org/docs/handbook/2/generics.html。这捕获了约束commonName and animal或多或少(好吧,C本身可以是联合类型,这会破坏事物,但是对于this我们会忽略潜在的不健全情况。出于我们的目的,我们可以假设C将是"cat" or "dog"或任何其他单一成员keyof TypeMap)。但编译器不允许makeSound()键入检查。

所以我们必须重构(或者用类型断言 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#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] and this.animal。这就是我一直称之为“相关联合”的问题,如中所述微软/TypeScript#30581 https://github.com/microsoft/TypeScript/issues/30581。此问题的修复已于微软/TypeScript#47109 https://github.com/microsoft/TypeScript/pull/47109并涉及给予petSound a 分布对象类型编译器可以跟踪相关性。它看起来像这样:

const petSound: { [C in keyof TypeMap]: (animal: TypeMap[C]) => void } =
{
    cat: cat => cat.purr(),
    dog: dog => dog.bark()
};

仅仅通过给予petSound a 映射类型 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html迭代keyof TypeMap, 以上makeSound()编译没有错误:

class Pet<C extends keyof TypeMap> {
    makeSound() {
        petSound[this.commonName](this.animal); // okay
    }
}

看起来不错!

(请注意,这仍然不合理;有人可以指定C as keyof TypeMap然后给出一个完全错误的animal对于给定的属性commonName财产。但是 TypeScript 故意忽略了这种不合理性,以便使这个相关联合的事情发挥作用。看微软/TypeScript#48730 https://github.com/microsoft/TypeScript/issues/48730了解更多信息)。

Playground 代码链接 https://www.typescriptlang.org/play?#code/MYGwhgzhAEDCYBdoG9oAcCuAnLAKAlCgPQBUAdBSUdAL60BQokMAIgPYDmK0ARmFgGsCxcpWp0a9AJYA7BAFMsAMzDB50ACoBPNPICyYNCnoBIYIgBccRAG5TAE05X2HO5MZsZEJLoQBlNgwZeytUAG1YaFloAXktNiVNHX1DAF0rXDAZKQBbMBArbV0DNAjUwgBeAD5oADc2KXtaaAr6ZFNzBCtOlprOskwcAgAaBydoRy5qic4yPkECeho7RnAoaAAFeQQAHkj5AA8FYJhY+MSilLQa9rM2HJzPADkwHPkrWDsTLNz8wuSSmUvsBPN4sBhgAg2HgQQ9nq93nBhtAfnkCklioYyoRbiYEAALKQQMiwx4yF5vFrQUnwt5fPGE4mo-JU5kgL6SEx5WIBIL2YS43y84JhAlEkn3MkU+SpXBipnZNH4GzQIjUNgCMBaUySSRAA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 TypeScript 中链接/连接/关联两个类字段的类型? 的相关文章

随机推荐