使用装饰器将属性分配给非原型
Assigning properties to non-prototype with decorators
我正在 frontend/backend 数据结构之间建立一个简单的映射。为此,我创建了一个如下所示的装饰器:
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
class(在本例中为 yearEstablished
)。这使得不可能在两个不同的 class 中拥有两个具有相同名称但不同 API 字段的属性。
有什么办法可以避免这种行为吗?
问题是 class 里面的 public
不是标准的 JavaScript,它只是 TypeScript 做的事情。因此,您必须小心,因为您所做的任何事情将来都可能会失败。
一种可能是使用Object.assign()
添加实例属性(IINM,apiFieldsBag
应该从对象字面量创建的对象转移到this
):
class AbstractCar {
constructor() {
Object.assign(this, {
@ApiField('id')
id: undefined,
});
}
}
现在,所有三个 classes 都在向同一个对象添加属性。解决这个问题的关键是 clone target.data
上的对象,这样每个 class 都使用不同的对象,而不是所有的对象都引用同一个对象。
下面是一个更简单的示例,演示了执行此操作的一种方法:
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"}
我正在 frontend/backend 数据结构之间建立一个简单的映射。为此,我创建了一个如下所示的装饰器:
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
class(在本例中为 yearEstablished
)。这使得不可能在两个不同的 class 中拥有两个具有相同名称但不同 API 字段的属性。
有什么办法可以避免这种行为吗?
问题是 class 里面的 public
不是标准的 JavaScript,它只是 TypeScript 做的事情。因此,您必须小心,因为您所做的任何事情将来都可能会失败。
一种可能是使用Object.assign()
添加实例属性(IINM,apiFieldsBag
应该从对象字面量创建的对象转移到this
):
class AbstractCar {
constructor() {
Object.assign(this, {
@ApiField('id')
id: undefined,
});
}
}
现在,所有三个 classes 都在向同一个对象添加属性。解决这个问题的关键是 clone target.data
上的对象,这样每个 class 都使用不同的对象,而不是所有的对象都引用同一个对象。
下面是一个更简单的示例,演示了执行此操作的一种方法:
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"}