在泛型中使用泛型类类型

时间:2021-07-12 19:11:53

标签: typescript typescript-generics

我有以下代码:

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

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

3 个答案:

答案 0 :(得分:2)

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

我觉得你有点糊涂了:

  • function doStuffAndInstantiate<T extends Entity>(Input: new ()=> Data<T>)

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

<块引用>

Input.method() 调用错误:类型“new () => Data ”上不存在属性“method”

() => Data 它是一种口述类型:

“一个没有参数的函数,它从类 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 NOT 相关- 曾经与 Data 在一起,因为他们只是 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>

我刚刚声明的是:

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

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

如果您想获得子类 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();
}

有点难猜到你在找什么嘿嘿

答案 1 :(得分:0)

您需要声明您想要一个可使用 new 调用并返回您的 Data<T> (new () => Data<T>) 的对象,并且还有一个名为 method 的属性是一个不带参数且返回值被忽略的函数 (() => 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();

答案 2 :(得分:0)

new ()=> Data<T> 类型失败,因为此类型定义不包括 Data 类的静态。 typeof Data 失败,因为 prototypeData 之间的 ChildData 属性不同。

一种方法是排除 prototype 属性,然后重新添加到 Data<T> 约束中。

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