完全做作的示例传入 - 它是解释发生什么的最简单方法。
我想我可能会在错误的层面抽象完全,让我知道!
我有一个许多其他类实现的抽象类。扩展抽象类的每个类都有多个可以使用的标识符,因此我希望每个类都用静态成员封装它们的标识符,而不是创建一个带有许多可能值的映射,而不是创建一个映射到一个类的映射。
我有一组实现一些抽象类Dog
的类(Cat
,Hamster
,Pet
)。它们包含在实用程序类PetIdentifier
中。此抽象类Pet
具有带签名isNameOk(name: string)
的静态方法。然后扩展Pet
类的每个类都有一个AcceptableNames
的静态列表。在静态方法isNameOk
中,我希望能够检查该名称是否在该类的可接受名称列表中。然后我希望能够传递给PetIdentifier
,一个名字,然后找回我可以实例化的可能的宠物类的集合。注意:在完成过滤之前,我不想实例化。
class PetIdentifier {
constructor (private pets: Array<The Pet Class/Constructor Type> = [Dog, Cat, Hamster]) {}
getPossiblePets (name: string) : Array<The Pet Class/Constructor Type> {
return this.pets.filter((pet) => pet.isNameOk(name))
}
}
abstract class Pet {
private static names: Array<string>
constructor () {}
static isNameOk (name: string) : boolean {
return this.names.indexOf(name) > -1
}
}
class Dog extends Pet {
names = ['rex', 'fluffy', 'odin']
}
class Cat extends Pet {
names = ['fluffy', 'garfield', 'socks']
}
class Hamster extends Pet {
names = ['thor', 'odin', 'loki']
}
const petIdentifier = new PetIdentifier()
const possiblePetTypes = petIdentifier.getPossiblePets('fluffy')
// instantiate them from here onward and use them
possiblePetTypes.forEach()
答案 0 :(得分:3)
在我们开始之前,您应该为Dog
,Cat
和Hamster
添加方法或一些区别特征(例如bark()
Dog
, meow()
的{{1}}和Cat
的...... spin()
,以便TypeScript编译器可以区分它们structurally。
您可以使用Hamster
来引用Pet
类的静态部分,因此上述代码将基本上使用typeof Pet
代替typeof Pet
进行编译。遗憾的是,您无法在The Pet Class/Constructor Type
调用中对其进行实例化,因为forEach()
类是抽象的:
Pet
所以下一步是描述possiblePetTypes.forEach(x => new x()); // error, can't do that
的可构造子类的静态方面:
Pet
现在您将type PetConstructor = {
new(): Pet;
isNameOk(name: string): boolean;
}
替换为The Pet Class/Constructor Type
,您就会发现可以实例化它:
PetConstructor
完成,对吗?也许。你所知道的是possiblePetTypes.forEach(x => new x()); // works fine
产生了一个possiblePetTypes
子类数组,但你不知道它们可能是哪一个。
您可以像这样使Pet
和PetConstructor
通用:
PetIdentifier
(请注意,我在type PetConstructor<P extends Pet> = {
new(): P;
isNameOk(name: string): boolean;
}
class PetIdentifier<PC extends PetConstructor<{}>> {
constructor (private pets: Array<PC>) {}
getPossiblePets (name: string) : Array<PC> {
return this.pets.filter((pet) => pet.isNameOk(name))
}
}
的构造函数中删除了[Dog, Cat, Hamster]
的默认值,因为我们可能希望通用PetIdentifier
成为其他类型的PC
。我们如果我们需要,可以解决这个问题。
Pet
现在您知道const petIdentifier = new PetIdentifier([Dog, Cat, Hamster])
const possiblePetTypes = petIdentifier.getPossiblePets('fluffy')
possiblePetTypes.forEach(x => new x()); // created Pet is Dog | Cat | Hamster
是possiblePetTypes
或Dog
或Cat
。做完了吧?也许,但听起来你想知道在编译时 Hamster
包含possiblePetTypes
或Dog
但不是 {{1 },因为Cat
不是Hamster
的可能名称。
好吧,我不知道这样做的好方法。 TypeScript的控制流分析在任何地方都不够复杂,无法实现'fluffy'
更窄,而静态类型系统并不像您需要的那样功能齐全轻松表达它。我想使用conditional mapped types,以便TypeScript可以通过检查Hamster
,possiblePetTypes
和Dog | Cat
的类型来开始构建Dog.names
。但它似乎还没有出现。
这就是我现在能做的事情。希望能帮助到你;祝你好运!
自从我写这篇文章以来,我一直在使用类型系统,并且有一些用于推断正确类型的工作。您可能感兴趣,或者您可能认为它有点过分。这是新代码:
首先,一些辅助函数和类型:
Cat.names
现在进行繁重的工作。我将所有静态方法/属性更改为实例方法/属性。这可能不是您想要的,但以这种方式操作类型要容易得多。如果您喜欢这种方法并且仍然需要静态方法,那么您可以这样做,但它会更加繁琐。
Hamster.names
请注意,我添加了一个type Lit = string | number | boolean | undefined | null | void | {};
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit, L extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L): [A, B, C, D, E, F, G, H, I, J, K, L];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit, K extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K): [A, B, C, D, E, F, G, H, I, J, K];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit, J extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J): [A, B, C, D, E, F, G, H, I, J];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit, I extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I): [A, B, C, D, E, F, G, H, I];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit, H extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): [A, B, C, D, E, F, G, H];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit, G extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F, g: G): [A, B, C, D, E, F, G];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit, F extends Lit>(a: A, b: B, c: C, d: D, e: E, f: F): [A, B, C, D, E, F];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit, E extends Lit>(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E];
function tuple<A extends Lit, B extends Lit, C extends Lit, D extends Lit>(a: A, b: B, c: C, d: D): [A, B, C, D];
function tuple<A extends Lit, B extends Lit, C extends Lit>(a: A, b: B, c: C): [A, B, C];
function tuple<A extends Lit, B extends Lit>(a: A, b: B): [A, B];
function tuple<A extends Lit>(a: A): [A];
function tuple(...args: any[]): any[] {
return args;
}
type Constructor<T> = {
new(...args: any[]): T;
readonly prototype: T;
}
type Diff<T extends string, U extends string> = ({[K in T]: K} &
{[K in U]: never} & { [K: string]: never })[T];
属性,该属性在运行时只是null。 TypeScript使用它来维护从每个可能的宠物名称到宠物类型的映射。
abstract class Pet {
names: string[];
constructor() {}
isNameOk(name: string): boolean {
return this.names.indexOf(name) > -1;
}
["constructor"]: Constructor<this>;
nameMap: Record<string, Pet> &
Record<this['names'][number], this> &
Record<Diff<AllPets['names'][number], this['names'][number]>, never> = null!;
// type helper, just null at runtime
}
现在nameMap
只返回一个只有相关宠物类型的数组。
class PetIdentifier<P extends Pet> {
constructor (private pets: Array<P>) {}
getPossiblePets<N extends string>(name: N) : Array<P['nameMap'][N]> {
return this.pets.filter((pet) => pet.isNameOk(name))
}
}
请注意,我使用getPossiblePets()
辅助函数来声明class Dog extends Pet {
names = tuple('rex', 'fluffy', 'odin') // use tuple for literals
bark() { }
}
class Cat extends Pet {
names = tuple('fluffy', 'garfield', 'socks')
meow() { }
}
class Hamster extends Pet {
names = tuple('thor', 'odin', 'loki')
spin() { }
}
type AllPets = Dog | Cat | Hamster; // need an AllPets type
属性;这允许将tuple()
推断为字符串文字的元组而不是字符串数组。 TypeScript需要字符串文字来进行映射。
另请注意,我需要names
类型,它是names
所有已声明子类的显式并集。这需要在AllPets
的{{1}}属性中,因此,Pet
知道名称,例如nameMap
,并且可以回答&#34;否& #34;当被问及Pet
是否可以命名为Cat
时。 (不,没有办法设置它,以便thor
可以对任何它不能识别的名称说“不”#。
最后,结果:
Cat
了解thor
不再包含Cat
的方式。
如果你给它一个完全随机的名字,比如:
const petIdentifier = new PetIdentifier([new Dog(), new Cat(), new Hamster()]);
const possiblePetTypes = petIdentifier.getPossiblePets('fluffy')
// possible pet types is (Dog | Cat)[], no Hamster!
它将返回通用possiblePetTypes
类型。不,我无法让它返回Hamster
之类的内容,抱歉。
好的,那种作品并且完全是疯狂的,任何人都可能无法维持。我想我建议只是放弃在设计时知道const impossiblePetTypes = petIdentifier.getPossiblePets('galactus');
// impossiblePetTypes is Pet[]
来自Pet[]
的{{1}}类型,并且认为这是一种更好处理的事实。在运行时。如果传入一个在设计时未知其值的字符串:
never[]
然后你从所有这种类型的杂耍中获得零利益。是的。将上述内容视为有趣的转移,但我不愿意尝试将其用于任何您关心部署到某种生产系统的事情。祝你好运!