【问题标题】:Using Generic Class Types in Generics在泛型中使用泛型类类型
【发布时间】:2021-09-21 22:34:07
【问题描述】:

我有以下代码:

class Entity {
    id: number
}

class ChildEntity extends Entity {
    child_property: number;
}

class ChildEntity2 extends Entity {
    child_property_2: number;
}

class Data<T extends Entity> {
    protected entity: T
    static method() {
    }
}

class ChildData extends Data<ChildEntity> {
    childMethod() {
        this.entity.child_property = 1;
    }
}

class ChildData2 extends Data<ChildEntity2> {
    childMethod() {
        this.entity.child_property_2 = 1;
    }
}

function doStuffAndInstantiate(Input /* What should be the type here??? */ ) {
// function doStuffAndInstantiate<T extends Entity>(Input: new ()=> Data<T> ) { /* Error at Input.method() call: Property 'method' does not exist on type 'new () => Data '. */
// function doStuffAndInstantiate(Input: typeof Data ) { /* error at doStuffAndInstantiate(ChildData) call: 'ChildEntity' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Entity'. */
    Input.method();
    return new Data();
}

doStuffAndInstantiate(ChildData);
doStuffAndInstantiate(ChildData2);

doStuffAndInstantiateInput 参数的类型应该是什么?我尝试了很多方法都没有成功:(

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    您需要声明您想要一个可使用new 调用并返回您的Data&lt;T&gt; (new () =&gt; Data&lt;T&gt;) 的对象,并且还有一个名为method 的属性,它是一个不带参数的函数一个被忽略的返回值 (() =&gt; void)。

    试试这个:

    function doStuffAndInstantiate<T extends Entity>(Input: { method(): void; new(): Data<T> }): Data<T> {
      Input.method();
      return new Input(); // You'll also want to instantiate "Input", not "Data" directly
    }
    

    编辑:或者,如果您想要更通用,您可以生成更准确的返回类型:

    function doStuffAndInstantiate<
      T extends Entity,
      U extends Data<T>,
      V extends { method(): void; new(): U },
      W = V extends { new(): infer W } ? W : never
    >(Input: V): W {
      Input.method();
      return new Input() as unknown as W;
    }
    
    // TS is happy with this now
    doStuffAndInstantiate(ChildData2).childMethod();
    

    【讨论】:

    • 是的...但我希望我的类型知道Input 的所有静态类型。如果我这样做,我每次向Input 添加新的静态方法时都必须更改此类型。
    • 什么意思?编译器无法神奇地知道未声明的类型,如果您想访问 Input 的属性,那么您必须以某种方式告诉编译器它存在。您可能的意思是要返回Input 的实际类型吗?我已经将我的答案更新为“更通用”,看看。 (另外,很抱歉回复慢)
    • “编译器无法神奇地知道未声明的类型”我不明白为什么。 typeof Input(我的第二个评论示例)将了解所有静态方法。但ChildData 在函数调用时将无法分配给它。
    【解决方案2】:

    你得到的错误是什么意思?

    我觉得你有点糊涂了:

    • function doStuffAndInstantiate&lt;T extends Entity&gt;(Input: new ()=&gt; Data&lt;T&gt;)

    无法编译,因为它误用了关键字 new。打字稿中的new 用于创建类的实例,就像您在函数实现中所做的那样。据我了解,new 在类型注释(参数的类型注释)中没有多大意义(除非你能教我一些新东西:)!)

    Input.method() 调用出错:“new () => 数据”类型上不存在属性“方法”

    () =&gt; Data这是一个类型指示:

    “从数据类返回对象的无参数函数”

    因此,由于它是一个函数,它没有任何可调用的名为method 的属性。 (根据您在示例代码中明确提供的内容)

    • function doStuffAndInstantiate(Input: typeof Data )

    你刚刚用这一行声明的是:

    “一个函数,它接受一个名为 'Input' 的参数,该参数是对 Data 类的引用,并且不返回任何内容”

    doStuffAndInstantiate(ChildData) 调用出错:“ChildEntity”可分配给“T”类型的约束,但“T”可以用约束“Entity”的不同子类型实例化。

    这是真的,因为 ChildData 和 ChildData2 都是对 Data 类的引用。

    我尝试根据当前情况提供解决方案26/07 10:46 提问

    我仍然很难弄清楚你想要做什么。您正在尝试从 Data 调用静态方法,但您正在传递来自 ChildDataChildData2 的类引用,这 ARE NOTData 相关因为他们只是extend Entity

    目前,我可以为您提供的有以下几点:

    function doStuffAndInstantiate<T extends Entity>(): Data<T> {
      // Since it's a static method you don't need to pass a parameter to call it
      Entity.method()
      // We're getting the generic type from the function
      return new Data<T>();
    }
    

    你可以直接消费:

    const childEntity = doStuffAndInstantiate<ChildEntity>(); // returns Data<ChildEntity>
    const childEntity2 = doStuffAndInstantiate<ChildEntity2>(); // returns Data<ChildEntity2>
    

    我刚刚声明的是:

    “一个不带参数的泛型函数,它返回具有给定泛型类型T 的类Data 的对象”

    这就是编译器可以在没有任何注释或预处理器魔法的情况下搜索具有合适构造函数的有效候选者的情况。

    如果您想获取子类 ChildData 和 ChildData2you'd need toswitchorif-elseif-elsethrough all the classes that you any be interested in returning. Since this would be done at runtime you can't properly type-annotate it because typescript won't let you useT 的直接实例作为值,因为它引用了一个类型(不存在转译为 JavaScript 时)。

    它看起来像:

    function doStuffAndInstantiate(classOfSomething: unknown): any | null {
    
        switch(classOfSomething) {
            case ChildData2: 
                return new ChildData2();
            case ChildData: 
                return new ChildData();
            default: 
                return null;
        }
       
    
    }
    

    或者您可以将其声明为可实例化的?

    export type Abstract<T = any> = Function & { prototype: T };
    export type Instantiable<T = any> = new (...args: any[]) => T;
    export type Class<T = any> = Abstract<T> & Instantiable<T>;
    
    
    function  doStuffAndInstantiate<E extends Entity, T extends Data<E>>(classOfSomething: Class<T> & { method: () => void }): T {
        classOfSomething.method();
        return new classOfSomething();
    }
    
    

    很难猜出你在找什么,呵呵

    【讨论】:

    • 好的,但是如果 methodChildEntity 中被覆盖怎么办?在那种情况下,我总是使用基类的method 而不是孩子的。
    • Input.method() 如果我这样做,TS2339: Property 'method' does not exist on type 'Class&lt;T&gt;'. Property 'method' does not exist on type 'Abstract&lt;T&gt;'. 将失败:function doStuffAndInstantiate&lt;E extends Entity, T extends Data&lt;E&gt;&gt;(Input:Class&lt;T&gt; ) {
    • 让我更新最后提供的解决方案。它抱怨它没有method,所以基本上我们需要结合两种类型:像这样Class&lt;T&gt; &amp; { method: () =&gt; void}
    • 这是我想避免的。要重新定义我想在函数中使用的所有静态方法:-/.在这种情况下,如果我从类中删除 method,打字稿将不会看到任何错误,尽管我正在调用一个不存在的方法。
    • 如果你传递了一些没有method 方法的类会给你一个编译时错误,所以如果你从其中一个类中删除它就不会再编译了。
    【解决方案3】:

    new ()=&gt; Data&lt;T&gt; 类型失败,因为此类型定义不包括 Data 类的静态信息。 typeof Data 失败,因为 prototype 属性在 DataChildData 之间不同。

    一种方法是排除 prototype 属性,然后添加回 Data&lt;T&gt; 约束。

    function doStuffAndInstantiate<
      T extends Entity,
      R extends (new () => Data<T>) & Omit<typeof Data, 'prototype'>
    >(Input: R): Data<T> {
      Input.method();
      return new Data();
    }
    

    如果您的子类不覆盖Data 类的任何静态方法,直接使用Data 的方法会更简洁:

    function doStuffAndInstantiate<T extends Entity>(Input: new () => Data<T>){
        Data.method();
        return new Input();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      相关资源
      最近更新 更多