Присвоение свойств непрототипу с помощью декораторов
Я строю простое отображение между структурой данных внешнего / внутреннего интерфейса. Для этого я создал декоратор, который выглядит следующим образом:
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,
});
}
}