use*_*075 5 inheritance decorator typescript
我在玩Typescript装饰器,它们的行为似乎与与类继承一起使用时所期望的完全不同。
假设我有以下代码:
class A {
@f()
propA;
}
class B extends A {
@f()
propB;
}
class C extends A {
@f()
propC;
}
function f() {
return (target, key) => {
if (!target.test) target.test = [];
target.test.push(key);
};
}
let b = new B();
let c = new C();
console.log(b['test'], c['test']);
Run Code Online (Sandbox Code Playgroud)
哪个输出:
[ 'propA', 'propB', 'propC' ] [ 'propA', 'propB', 'propC' ]
Run Code Online (Sandbox Code Playgroud)
虽然我期望这样:
[ 'propA', 'propB' ] [ 'propA', 'propC' ]
Run Code Online (Sandbox Code Playgroud)
因此,似乎target.test在A,B和C之间共享。我对这里发生的情况的理解如下:
new B()触发A 的实例化,从而触发f对A 的求值。由于target.test未定义,因此对其进行了初始化。f然后评估B的值,由于它扩展了A,因此首先实例化了A。因此,当时target.test(target为B)test为A定义了引用。因此,我们将propB其推入。此时,一切按预期进行。test,不同于为B定义的对象。但是日志证明我错了。谁能向我解释为什么会这样(1)以及如何实现fA和B具有单独的test属性?
我猜您会称其为“特定于实例”的装饰器?
好了,所以花了几个小时玩转并搜索网络后,我得到了一个有效的版本。我不明白为什么这样做有效,因此请原谅缺乏解释。
关键是使用Object.getOwnPropertyDescriptor(target, 'test') == null而不是!target.test检查test属性是否存在。
如果您使用:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
};
}
Run Code Online (Sandbox Code Playgroud)
控制台将显示:
[ 'propB' ] [ 'propC' ]
Run Code Online (Sandbox Code Playgroud)
这几乎是我想要的。现在,该数组特定于每个实例。但这意味着'propA'数组中缺少它,因为它是在A中定义的。因此,我们需要访问父目标并从那里获取属性。这花了我一段时间才能弄清楚,但是您可以通过使用它来解决Object.getPrototypeOf(target)。
最终的解决方案是:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
/*
* Since target is now specific to, append properties defined in parent.
*/
let parentTarget = Object.getPrototypeOf(target);
let parentData = parentTarget.test;
if (parentData) {
parentData.forEach(val => {
if (target.test.find(v => v == val) == null) target.test.push(val);
});
}
};
}
Run Code Online (Sandbox Code Playgroud)
哪个输出
[ 'propB', 'propA' ] [ 'propC', 'propA' ]
Run Code Online (Sandbox Code Playgroud)
愿任何了解上述原因的人无法启发我。
您的代码具有这种行为,因为您的装饰字段是实例成员,target您收到的是该类的原型。当执行开始时,类A将首先加载,因为它是父类。因此该test数组设置在类 A 上,由所有子类B/Cprototype共享。因此您会在数组中看到 3 个元素。test
一种替代方法是在类本身getOwnPropertyDescriptor()上注册元,而不是使用。target.constructor然后每个类都会拥有自己的元数据,在收集装饰字段时,您只需搜索到原型链并将它们全部收集起来即可。(使用标准relect-metadata作为助手)。
function f() {
return (target, key) => {
if (!Reflect.hasOwnMetadata('MySpecialKey', target.constructor)) {
// put field list on the class.
Reflect.defineMetadata('MySpecialKey', [], target.constructor);
}
Reflect.getOwnMetadata('MySpecialKey', target.constructor).push(key);
};
}
/**
* @param clz the class/constructor
* @returns the fields decorated with @f all the way up the prototype chain.
*/
static getAllFields(clz: Record<string, any>): string[] {
if(!clz) return [];
const fields: string[] | undefined = Reflect.getMetadata('MySpecialKey', clz);
// get `__proto__` and (recursively) all parent classes
const rs = new Set([...(fields || []), ...this.getAllFields(Object.getPrototypeOf(clz))]);
return Array.from(rs);
}
Run Code Online (Sandbox Code Playgroud)
另一种选择是使用类验证器方式,它具有包含所有装饰器相关信息的全局元数据存储。在执行逻辑时,检查目标构造函数是否是注册目标的实例。如果是这样,请包括该字段。
| 归档时间: |
|
| 查看次数: |
2374 次 |
| 最近记录: |