简体   繁体   中英

TypeScript: Decorating a derived class with typed decorator function

I am trying to build an "Entity Framework"-like ORM library for node.js and MongoDB with TypeScript.

With this library, the consumer will be able to define a Model class (ex. Person ) that will extend a Base class, and the class decorator will add additional data to the Person class (for example, instances array, that will contain all the Model's instances, and collection_name that will be the MongoDB collection name for the model, etc.).

Code in TypeScript playground .


So my first step was creating a Base class:

class Base<T>
{
    static collection_name: string
    static instances: Base<any>[]
    _id: string
}

So the user will be able to define his model like so:

@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person>
{
    @field name
    @field address
    ...
}

then I created a decorator class to set the collection_name and instances properties on the Person class:

function decorator<T>(config: { collection_name: string }) {
    return function <T extends Base<T>>(Class: IClass<T>) {
        Class.collection_name = config.collection_name;
        Class.instances = [];
        return Class
    }
}

the decorator function receives the user-generated Class , and I am trying to create an interface that will describe the type of such class. I called it IClass :

interface IClass<T>
{
    new(): Base<T>

    instances: Base<T>[];
    collection_name: string
}
  • new is the constructor (that returns a Base instance)
  • instances and collection_name are static properties of Base<T> and are non-static here (I'm not sure about this, is this right?)

However, when trying to define the user Model I get the following error:

@decorator<Person>({collection_name: 'people'})   // <==== ERROR HERE ===
class Person extends Base<Person>
{
}

Error:(23, 2) TS2345: Argument of type 'typeof Person' is not assignable to parameter of type 'IClass>'.

Property 'instances' is missing in type 'typeof Person' but Type 'typeof Person' is missing the following properties from type required in type 'IClass>'.

It seems like the typescript compiler is ignoring the static members inherited from Base when checking the type of typeof Person .

How can I define the type of the Class property of the decorator function ?

The problem as jcalz points out is that your decorator is accepting a Class of a type that already has the static properties instances and collection_name . You need to use two different interfaces, one which is a type that simply constructs instances of T with the new(): T signature, and another that extends this interface to include the static properties your decorator will add.

class Base<T> {
    static _id = 0;
    private _id: number;

    constructor () {
        this._id = Base._id++;
    }
}

interface BaseConstructor<T extends Base<T>> {
    _id: number;
    new(): T;
}

interface DecoratedBaseConstructor<T extends Base<T>> extends BaseConstructor<T> {
    instances: T[];
    collection_name: string;
}

function decorator<T extends Base<T>>(config: { collection_name: string }) {
    return (baseConstructor: BaseConstructor<T>): DecoratedBaseConstructor<T> => {
        const decoratedBaseConstructor = baseConstructor as Partial<DecoratedBaseConstructor<T>>;
        decoratedBaseConstructor.collection_name = config.collection_name;
        decoratedBaseConstructor.instances = [];
        return decoratedBaseConstructor as DecoratedBaseConstructor<T>;
    };
}

@decorator<Person>({collection_name: 'people'})
class Person extends Base<Person> {
    name: string;

    constructor () {
        super();
        this.name = 'foo';
    }
}

With this approach, all of Base 's static members must be public. Any static members of Base initialized in the decorator should go in the DecoratedBaseConstructor , and any remaining static members not initialized in the decorator should go in the BaseConstructor instead.

I assume that you use the generic type T in the Base class somehow in your actual code, but if you don't, you should remove the generic type from the Base class and everything else will still work the same.

Check out the above snippet in this playground .

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