简体   繁体   中英

TypeScript generic class that creates instances of its type variable's class?

I'm familiar with generic classes in TypeScript, where a class can be defined with an associated type variable, and then instances with a specific type can manipulate and return values of that type.

Problem: I want a generic class that creates instances of the type variable. For instance:

class Thing {
  thingProp: string;
}

class ThingOne extends Thing {
  thingOneProp: string;
}

class ThingTwo extends Thing {
  thingTwoProp: string;
}

class Maker<T extends Thing> {

  make(): T {
    return new T();
    //         ^--- " // <- "error TS2304: Cannot find name 'T'""
  }

}

let thingOneMaker = new Maker<ThingOne>();

let thingOne: ThingOne = thingOneMaker.make();

let thingTwoMaker = new Maker<ThingTwo>();

let thingTwo: ThingTwo = thingTwoMaker.make();

let thingError: ThingOne = thingTwoMaker.make();
//      ^--- "error TS2322: Type 'ThingTwo' is not assignable to type 'ThingOne'"

This almost seems to work. The compiler generates code, and the error on the last line shows that TypeScript understands what type thingTwoMaker.make() should return.

However, the error on return new T(); shows that TypeScript doesn't understand that I'm trying to make instances of the type variable's class, and the generated JavaScript confirms it:

var Maker = (function () {
    function Maker() {
    }
    Maker.prototype.make = function () {
        return new T(); // <- "error TS2304: Cannot find name 'T'"
    };
    return Maker;
}());

And, not surprisingly, running the generated JavaScript with Node.js produces a ReferenceError: T is not defined error.

How can I make a generic class whose instances can create instances of the type variable class? (Tested using TypeScript 2.0.10.)

Somehow, you need to give the Maker class the constructor of the type you'd like it to make, so that it has a value on which to call new .

I'd say a good option would be to pass the class constructor as an argument to Maker 's constructor. That will allow it to construct instances of that class, and it will automatically infer the type that it's building so you don't have to manually annotate the generic type anymore.

So maybe something like this:

class Maker<T extends Thing> {

  private ctor: {new(): T};

  constructor(ctor: {new(): T}) {
    this.ctor = ctor;
  }

  make(): T {
    return new this.ctor();
  }
}

Then, you can pass the right class constructor to each kind of Maker , and the type will be automatically inferred:

let thingOneMaker = new Maker(ThingOne);
let thingOne: ThingOne = thingOneMaker.make();

let thingTwoMaker = new Maker(ThingTwo);
let thingTwo: ThingTwo = thingTwoMaker.make();

// Still an error.
let thingError: ThingOne = thingTwoMaker.make();

Playground link.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM