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

泰泰

我正在尝试找出将类型分配给该通用类工厂的最佳方法。我从另一个问题中复制了部分代码: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];

    // animalInstance : Dog | Cat
    const animalInstance = new animalKlass() as InstanceType<KindMap[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(params);

    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());

游乐场链接

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


2020年5月29日更新:

@Kamil Szot指出,我的未重载函数首先没有适当的类型安全性:

    // Should be an error but is not:
    const cat = getAnimalInstance((() => Kind.DogKind)(), new CatParams());

因此,正如他所建议的,我们确实确实需要重载,但是我不想手动编写它们。所以,这就是我现在所拥有的。我认为这已经足够了,但是我希望我可以定义另一个类型,以使这些重载的自动生成变得不再那么冗长,并且使我不必重复执行两次函数实现的函数签名。

// We can use UnionToIntersection to auto-generate our overloads
// Learned most of this technique here: https://stackoverflow.com/a/53173508/544130
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

const autoOverloadedCreator: UnionToIntersection<
    Kind extends infer K ?
    K extends Kind ?
    // I wish there was a way not to have to repeat the signature of getAnimalInstance here though!
    (key: K, p: InstanceType<ParamsMap[K]>) => InstanceType<KindMap[K]> :
    never : never
> = getAnimalInstance;

// This works, and has overload intellisense!
let cat2 = autoOverloadedCreator(Kind.CatKind, new CatParams());

// And this properly gives an error
const yayThisIsAnErrorAlso = autoOverloadedCreator((() => Kind.DogKind)(), new CatParams());

// Note that this type is different from our ManuallyOverloadedFuncType though:
// type createFuncType = ((key: Kind.DogKind, p: DogParams) => Dog) & ((key: Kind.CatKind, p: CatParams) => Cat)
type CreateFuncType = typeof autoOverloadedCreator;

游乐场链接

卡米尔·佐特(Kamil Szot)

另一个更简单的常规解决方案Playground链接

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;

type Tuples<T> = T extends Kind ? [T, InstanceType<KindMap[T]>, InstanceType<ParamsMap[T]>] : never;
type SingleKinds<K> = [K] extends (K extends Kind ? [K] : never) ? K : never;
type ClassType<A extends Kind> = Extract<Tuples<Kind>, [A, any, any]>[1];
type ParamsType<A extends Kind> = Extract<Tuples<Kind>, [A, any, any]>[2];

function getAnimalInstance<A extends Kind>(key:SingleKinds<A>, params: ParamsType<A>): ClassType<A> {
    const animalKlass: ClassType<A> = kindMap[key];

    const animalInstance = new animalKlass();

    animalInstance.init(params); 
    return animalInstance;
}


// this works
const cat = getAnimalInstance(Kind.CatKind, new CatParams());

const shouldBeError = getAnimalInstance(Kind.DogKind, new CatParams()); // wrong params
const shouldBeErrorToo = getAnimalInstance(f(), new CatParams());       // undetermined kind
const shouldBeErrorAlso = getAnimalInstance(f(), new DogParams());      // undetermined kind

var k:Kind;
k = Kind.CatKind;

const suprisinglyACat = getAnimalInstance(k, new CatParams());    // even that works! 
const shouldError = getAnimalInstance(k, new DogParams());

function f():Kind {
    return Kind.DogKind;
}

这是另一个示例,用于反映我的其他需要手动重载的答案。它还可以自动获取Params类型,而无需单独的手动定义映射。

游乐场链接

class DogParam { public n: number = 0; }
class CatParam { public n: string = "a"; }
class BatParam { public n: boolean = true; }

class Dog { init(p: DogParam) { } }
class Cat { init(p: CatParam) { } }
class Bat { init(p: BatParam) { } }

enum Kind { Dog, Cat, Bat }

const kindMap = {
    [Kind.Dog]: Dog,
    [Kind.Cat]: Cat,
    [Kind.Bat]: Bat
} 

type Tuples<K = Kind> = K extends Kind ? [
    K,
    InstanceType<(typeof kindMap)[K]>,
    InstanceType<(typeof kindMap)[K]> extends 
        { init: (a: infer P) => any } ? P : never
] : never;
type SingleKinds<K> = [K] extends (K extends Kind ? [K] : never) ? K : never;
type ClassType<K> = Extract<Tuples, [K, any, any]>[1];
type ParamsType<K> = Extract<Tuples, [K, any, any]>[2];

function a<K extends Kind>(k: SingleKinds<K>, p: ParamsType<K>): ClassType<K> { 
    var ins:ClassType<K> = new kindMap[k];
    ins.init(p); 
    return ins;         
}


function f(): Kind {
    return Kind.Cat;
}

var k:Kind;
k = Kind.Cat;

a(Kind.Dog, new DogParam()); // works
a(Kind.Cat, new DogParam()); // error because mismatch
a(f(), new DogParam());      // error because kind undetermined
a(f(), new CatParam());      // error because kind undetermined
a(f() as Kind.Dog, new DogParam());      // works, but hey, it's your fault 
                                        // doing the wrong cast here manually
a(k, new CatParam());   // even this works
a(k, new DogParam());   // and this error

// you need to use exactly one kind at a time or it errors
var mixed: Kind.Dog | Kind.Cat = null as any;
var b = a(mixed, new DogParam());

var mixedfn = ():Kind.Dog | Kind.Cat => null as any;
var b = a(mixedfn(), new DogParam());

融合了我和Taytay想法的解决方案,该想法生成了从“种类到类”映射所需的一切,并使用自动生成的函数重载来提供良好的智能感知游乐场链接

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;

type Tuples<K = Kind> = K extends Kind ? [
    K, 
    InstanceType<KindMap[K]>, 
    InstanceType<(typeof kindMap)[K]> extends 
        { init: (a: infer P) => any } ? P : never
] : never;

type ClassType<K> = Extract<Tuples, [K, any, any]>[1];
type ParamsType<K> = Extract<Tuples, [K, any, any]>[2];

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Fnc<T = Tuples> = UnionToIntersection<
    T extends Tuples ? (key: T[0], p: T[2]) => T[1] : never
>;
var getAnimalInstance:Fnc = function<K extends Kind>(key: K, params:ParamsType<K>):ClassType<K> {
    const animalKlass = kindMap[key];

    const animalInstance = new animalKlass();

    animalInstance.init(params);

    return animalInstance;
}

// works
const cat = getAnimalInstance(Kind.CatKind, new CatParams());

// errors
const shouldBeError = getAnimalInstance((() => Kind.DogKind)(), new CatParams());

提出问题的用户Taytay在此处的Playground链接中对此代码进行了调查,以确定其工作方式。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何将F#中的MongoDB Bson文档映射到泛型类型

来自分类Dev

如何在Kotlin中将泛型类映射到相同类的泛型数组

来自分类Dev

如何将枚举类型映射到tinyint

来自分类Dev

如何将泛型参数的类型限制为Typescript函数以成为枚举

来自分类Dev

Scala泛型:如何声明类型必须为case类?

来自分类Dev

如何使用泛型类型声明类 - Java 1.7

来自分类Dev

类声明中的泛型类型

来自分类Dev

将一个类映射到其参数与该类的参数相同的泛型

来自分类Dev

Typescript如何将类型键映射到camelCase

来自分类Dev

如何禁止将枚举类型中的某些枚举值映射到XML表示形式?

来自分类Dev

将枚举用作Typescript中的泛型类型时不兼容的类型

来自分类Dev

Typescript泛型:返回数组类型以按位置将输入数组与映射类型匹配

来自分类Dev

如何使用泛型声明工厂方法

来自分类Dev

TypeScript typeof 泛型类的类型

来自分类Dev

如何使用泛型实现类型声明?

来自分类Dev

如何使用泛型返回泛型类的类类型?

来自分类Dev

如何将复杂的泛型方法应用于通配符泛型类型的类?

来自分类Dev

如何使用泛型实现类型安全?

来自分类Dev

Java泛型类工厂

来自分类Dev

Java:如何从泛型类型对象实例获取枚举类的值?

来自分类Dev

将枚举映射到Dagger注入的类(等效于MapBinder)

来自分类Dev

将变量类型作为泛型传递给递归工厂

来自分类Dev

如何声明一个泛型将使用的枚举?

来自分类Dev

如何为泛型类指定泛型集合类型?

来自分类Dev

如何为泛型类型创建工厂?

来自分类Dev

如何为泛型类型创建工厂?

来自分类Dev

使用泛型+联合类型从类中实现抽象方法时,类型安全性问题[Typescript]

来自分类Dev

如何使用类型的辅助类泛型

来自分类Dev

如何返回泛型类型的类

Related 相关文章

  1. 1

    如何将F#中的MongoDB Bson文档映射到泛型类型

  2. 2

    如何在Kotlin中将泛型类映射到相同类的泛型数组

  3. 3

    如何将枚举类型映射到tinyint

  4. 4

    如何将泛型参数的类型限制为Typescript函数以成为枚举

  5. 5

    Scala泛型:如何声明类型必须为case类?

  6. 6

    如何使用泛型类型声明类 - Java 1.7

  7. 7

    类声明中的泛型类型

  8. 8

    将一个类映射到其参数与该类的参数相同的泛型

  9. 9

    Typescript如何将类型键映射到camelCase

  10. 10

    如何禁止将枚举类型中的某些枚举值映射到XML表示形式?

  11. 11

    将枚举用作Typescript中的泛型类型时不兼容的类型

  12. 12

    Typescript泛型:返回数组类型以按位置将输入数组与映射类型匹配

  13. 13

    如何使用泛型声明工厂方法

  14. 14

    TypeScript typeof 泛型类的类型

  15. 15

    如何使用泛型实现类型声明?

  16. 16

    如何使用泛型返回泛型类的类类型?

  17. 17

    如何将复杂的泛型方法应用于通配符泛型类型的类?

  18. 18

    如何使用泛型实现类型安全?

  19. 19

    Java泛型类工厂

  20. 20

    Java:如何从泛型类型对象实例获取枚举类的值?

  21. 21

    将枚举映射到Dagger注入的类(等效于MapBinder)

  22. 22

    将变量类型作为泛型传递给递归工厂

  23. 23

    如何声明一个泛型将使用的枚举?

  24. 24

    如何为泛型类指定泛型集合类型?

  25. 25

    如何为泛型类型创建工厂?

  26. 26

    如何为泛型类型创建工厂?

  27. 27

    使用泛型+联合类型从类中实现抽象方法时,类型安全性问题[Typescript]

  28. 28

    如何使用类型的辅助类泛型

  29. 29

    如何返回泛型类型的类

热门标签

归档