简体   繁体   中英

How to constraint a generic type to have new()?

I want to have a function like:

createEntity<TEntity>(): TEntity {
    return new TEntity();
}

In C#, we could write:

void TEntity CreateEntity<TEntity>() where TEntity : new()

How can I do this in TypeScript?

The only way shown in the handbook to do something similar to this is to send the class you want to initialize as a parameter to the factory method, and describe it's constructor using the new keyword.

function factory<T>(type: { new (): T }): T {
    return new type();
}

class SomeClass { }

let result = factory(SomeClass);

The result will be of type SomeClass.

The constructor of the class will be type checked against the interface defined in the factory method.

If you want to initialize a class that takes a parameter in it's constructor you will have to specify that in the interface given to the factory method.

function factory<T>(type: { new (...args): T }, ...args): T {
    return new type(...args);
}


class SomeClass {
    constructor(name: string) { }
}

let a = factory(SomeClass, 'John Doe');

This has been answered here in Stack Overflow. To be able to new up a new class using generics, you need to have a guarantee that the type supports instantiation.

You can easily support parameter less constructors using this approach but with constructors that require parameters, it would become less useful as your creatEntity method should have to accept the values for those parameters and pass to the constructor while creating the new instance and as you can see each type can have its own signature for constructor.

class ObjectCreator{
    static createEntity<TEntity>(type:{new():TEntity;}):TEntity{
        return new type();
    }
}

class Person{
    firstName:string;
    lastName:string;
    constructor(){
        this.firstName = "TestFirstName";
        this.lastName = "TestLastName";
    }
}

var person: Person = ObjectCreator.createEntity(Person);
alert(person.firstName);

If you want to instantiate multiple classes with different parameters in your factory function then the other answers so far are incomplete. This is what you need.

class Hero {
  constructor(public point: [number, number]) {}
}

const entities = [];

const entityFactory = <
  T extends {
    new (...args: any[]): any;
  }
>(
  classToCreate: T,
  numberOf: number,
  ...args: ConstructorParameters<T>
): InstanceType<T>[] =>
  [...Array(numberOf)].map(() => new classToCreate(...args));

entities.push(...entityFactory(Hero, 10, [12, 10]));

console.log(entities[0].point);

We have a similar situation in which we want a generic class to take a factory function that takes parameters to instantiate the type. The provided solutions here does not cover that scenario.

The TypeScript approach seems to be to define Factories using an interface that expects a parameterless constructor:

export interface QueryTypeFactory<T> {
    new () : T;
}

TypeScript will not accept a factory function that returns an instance of type T to be used in place of an actual type - even though behind the scenes constructors are just functions that returns functions that returns T.

What we found out is that you can achieve this, but you need to an unsafe cast of the factory method like this:

function dynamicFactory<T>(f: () => T): FactoryType<T> {
    return f as any as FactoryType<T>;
}

Now you can provide a factory function which can encapsulate a closure to provide dynamic behavior when instantiating objects:

function factory(argument : any) : DynamicType {
    return new DynamicType(argument);
}

And now you use the dynamic factory like this:

let argument = { ... };
new ThingThatCreatesEntities(dynamicFactory(() => factory(argument)));

The trick is the cast of the factory method to any and then to the required factory type. It's not pretty, but it works and you can hide that in your implementation.

The nice thing about this approach is that the dynamic factory can be substituted for an actual type. So you could also do:

new ThingThatCreatesEntities(StaticType);

Where the StaticType is a class with a parameterless constructor.

This appears to work just fine.

 export abstract class GridRowEditDialogBase<T extends DataRow> extends DialogBase{ 
      protected activeRow: T = {} as T;
 }

if the T type is interface the instance of T is look like:

let a={} as T

if it is class with constructor I guess you need factor function:

factory1<T>(ctor?: NoParamConstructor<T>):T{return new ctor};
let a=this.factory<T>()

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