Typescript:如何声明以最安全的方式将枚举映射到类型的泛型类工厂?

问题描述 投票:0回答:1

我正在尝试找出将类型分配给该通用类工厂的最佳方法。我从另一个问题中复制了部分代码:https://stackoverflow.com/a/47933133将枚举值映射到类相对简单。但是,我似乎无法弄清楚如何进一步输入我的创建方法,以便它能识别出我正在创建的类实际上是否没有传入的参数。(我意识到这是构造实例的复杂方法。我想我已经将我在现实世界中的应用程序中尝试做的事情简化为这个问题。)

class Dog {
    public dogName: string = ""
    public init(params: DogParams) { }
}
class Cat {
    public catName: string = ""
    public init(params: CatParams) { }
}
class DogParams { public dogValues: number = 0 }
class CatParams { public catValue: number = 0}

enum Kind {
    DogKind = 'DogKind',
    CatKind = 'CatKind',
}

const kindMap = {
    [Kind.DogKind]: Dog,
    [Kind.CatKind]: Cat,
};
type KindMap = typeof kindMap;

const paramsMap = {
    [Kind.DogKind]: DogParams,
    [Kind.CatKind]: CatParams,
}
type ParamsMap = typeof paramsMap;

function getAnimalClasses<K extends Kind>(key: K, params: ParamsMap[K]): [KindMap[K], ParamsMap[K]] {
    const klass = kindMap[key];
    return [klass, params];
}

// Cool: Typescript knows that dogStuff is of type [typeof Dog, typeof DogParams]
const dogStuff = getAnimalClasses(Kind.DogKind, DogParams);

// Now imagine I want to instantiate and init my class in a type-safe way:
function getAnimalInstance<K extends Kind>(key: K, params: InstanceType<ParamsMap[K]>): InstanceType<KindMap[K]> {
    const animalKlass = kindMap[key];
    const paramsKlass = paramsMap[key];

    // animalInstance : Dog | Cat
    const animalInstance = new animalKlass() as InstanceType<KindMap[K]>;
    // paramsInstance: DogParams | CatParams
    const paramsInstance = new paramsKlass() as InstanceType<ParamsMap[K]>;

    // By this line, Typescript just knows that animalInstance has a method called init that takes `DogParams & CatParams`. That makes sense to me, but it's not what I want.
    // QUESTION: The following gives an error. Is there a type-safe way that I can make this method call and ensure that my maps and my `init` method signatures are 
    // are consistent throughout my app? Do I need more generic parameters of this function?
    animalInstance.init(paramsInstance);

    return animalInstance;
}

// This works too: It knows that I have to pass in CatParams if I am passing in CatKind
// It also knows that `cat` is an instance of the `Cat` class.
const cat = getAnimalInstance(Kind.CatKind, new CatParams());

Playground Link

请参见上面的代码中的实际问题。

typescript generics types factory
1个回答
0
投票

我认为不可能。

尝试像这样定义您的init:

public init<P extends DogParams>(params: P) { }
//..
public init<C extends CatParams>(params: C) { }

应该不会有太大变化,但现在TypeScript甚至不允许您像这样:init()(类型为animalInstance)对Dog | Cat进行任何调用:

function f(): Dog | Cat {
    return new Dog();
}
const dc: Dog | Cat = f();
dc.init(new DogParams());
// ^ here is the error

因为

This expression is not callable.   
Each member of the union type '(<P extends DogParams>(params: P) => void) | (<C extends CatParams>(params: C) => void)' has signatures, 
but none of those signatures are compatible with each other.(2349)

或者您可以更简单地像这样声明:

public init(params: string) { } // class Dog
//..
public init(params: number) { } // class Cat

现在是这里

const dc: Dog | Cat = f();
dc.init(5);

[dc.init的签名为init(params: never): void,您也不能调用它。


我认为,以一种类型安全的方式调用init的唯一方法是,如果您进行手动,运行时类型检查并针对每种情况进行单独的手动强制类型转换和调用,如下所示:

const dc: Dog | Cat = f();
if (dc instanceof Dog) {
    dc.init("5");
} else {
    dc.init(5);
}
© www.soinside.com 2019 - 2024. All rights reserved.