Присвоение свойств непрототипу с помощью декораторов

Я строю простое отображение между структурой данных внешнего / внутреннего интерфейса. Для этого я создал декоратор, который выглядит следующим образом:

function ApiField(
    apiKey: string,
    setFn: (any) => any = (ret) => ret,
    getFn: (any) => any = (ret) => ret
) {
    return function (target: AbstractModel, propertyKey: string) {
        target.apiFieldsBag = target.apiFieldsBag || {};
        _.assign(
            target.apiFieldsBag,
            {
                [propertyKey]: {
                    apiKey: apiKey,
                    setFn: setFn,
                    getFn: getFn
                }
            }
        );
    };
}

И вот как я это использую:

class AbstractCar {
    @ApiField('id')
    public id: string = undefined;
}

class BMW extends AbstractCar {
    @ApiField('cylinders')
    public cylinderCount: number;
}

class VW extends AbstractCar {
    @ApiField('yearCompanyFounded')
    public yearEstablished: number;
}

Проблема, которую я вижу, состоит в том, что вместо фактического объекта, передаваемого в декоратор, это всегда его прототип:

__decorate([
    ApiField('yearCompanyFounded')
], VW.prototype, "yearEstablished", void 0);

Это означает, что, поскольку я назначаю материал экземпляру в декораторе, он всегда присоединяется к прототипу, что, в свою очередь, означает, что свойства, которые я хочу определить, только VW Экземпляр также доступны на AbstractCar и BMW класс (в этом примере это будет yearEstablished). Это делает невозможным иметь два свойства с одинаковым именем, но разными полями API в двух разных классах.

Есть ли способ обойти это поведение?

2 ответа

Решение

Прямо сейчас все три класса добавляют свойства к одному и тому же объекту. Ключом к этому является клонирование объекта на target.data так что каждый класс использует свой объект, а не все ссылаются на один и тот же объект.

Вот более простой пример, демонстрирующий один из способов сделать это:

function ApiField(str: string) {
    return function (target: any, propertyKey: string) {
        // I tested with Object.assign, but it should work with _.assign the same way
        target.data = _.assign({}, target.data, {
            [propertyKey]: str
        });
    };
}

class AbstractCar {
    @ApiField("car")
    public carID;
}

class BMW extends AbstractCar {
    @ApiField("bmw")
    public bmwID;
}

class VW extends AbstractCar {
    @ApiField("vw")
    public vwID;
}

AbstractCar.prototype.data; // Object {carID: "car"}
BMW.prototype.data;         // Object {carID: "car", bmwID: "bmw"}
VW.prototype.data;          // Object {carID: "car", vwID: "vw"}

Проблема в том, что public внутри класса нет стандартного JavaScript, это только то, что делает TypeScript. Поэтому вы должны быть осторожны, потому что все, что вы делаете, может сломаться в будущем.

Одна из возможностей заключается в использовании Object.assign() добавить свойства экземпляра (IINM, apiFieldsBag должны быть переданы из объекта, созданного литералом объекта в this):

class AbstractCar {
    constructor() {
        Object.assign(this, {
            @ApiField('id')
            id: undefined,
        });
    }
}
Другие вопросы по тегам