具有复制属性的类的方法上的元数据在更改源类的元数据时发生更改-Typescript



很抱歉有冗长的标题。我有一个类MutateMe由decoratorDecorator传递到一个名为FilterFactory的工厂中。

export const Decorator = (options?: DecoratorOptions) => <T extends Constructor>(target: T) => {
new FilterFactory(target, options);
}

在这个工厂中,我将方法复制到target类上,并设置其元数据。

export class FilterFactory {
constructor(protected target: any, options: DecoratorOptions) {
// Getting the reference to the class from where I want to copy over methods with their own metadata
const routesController = FilterController;
// The class itself consists of a prefix that must be prepended to all its member methods' metadata.
const prefixRoute = getControllerPrefix(routesController);
console.log("For each key (member name)")
Reflect.ownKeys(routesController.prototype).forEach(
(property) => {
// Ignore the primitive class methods
if (!['constructor', 'toString', 'length'].includes(property.toString())) {
// Copy the methods over to the `target`
Object.defineProperty(
target.prototype,
property,
Object.getOwnPropertyDescriptor(
routesController.prototype,
property
)
)
// Prepends class metadata `filter` to each route method's metadata
patchRoutes(target.prototype[property], prefixRoute)
// NOTE: An alternative to prototype property assignment (Doesn't work either)
// target.prototype[property] = routesController.prototype[property]

console.log(Reflect.getOwnMetadata(PATH_METADATA, target.prototype[property]))
}
})
}
}

patchRoutes函数如下:

const patchRoutes = <K, T extends string, P>(patchee: any, patches: (T | T[] | ((...args: P[]) => (T | T[]))), ...args: P[]) => {
const existingPath = Reflect.getOwnMetadata(PATH_METADATA, patchee)
if (patches instanceof Function) { patches = patches(...args) }
if (!Array.isArray(patches)) patches = [patches]
Reflect.defineMetadata(PATH_METADATA, (existingPath === "/" ? [...patches] : [...patches, existingPath]).join("/"), patchee)
const createResetCallback = (resetValue, resetTarget) => () =>
Reflect.defineMetadata(PATH_METADATA, resetValue, resetTarget)
return createResetCallback(existingPath, patchee)
}

它返回一个reset回调以重置修补的元数据。

现在,当我用这个装饰器装饰多个类时,我可以看到修补的重复。

例如,打一次补丁会给我foo/filter/...,而第二次调用会给我bar/filter/filter/...

我想看看是否是不正确地复制方法的问题,所以,我尝试修补基类,复制修补的方法,并重置基类的元数据:

const propertyResetCb = patchRoutes(routesController.prototype[property], prefixRoute)
...
// Assigning the property now to the target
...
// Calling the reset callback
propertyResetCb()

然而,这似乎重置了我创建的所有装饰器的属性。

这让我相信它对复制的方法使用了一个单一的原型引用。我希望免费复制它们(如果你愿意的话,可以克隆(,这样我就可以独立设置它们的元数据。

此外,我更希望不必修改patchRoutes来考虑重复,因为最终,我希望分别对它们各自的元数据进行更多修改。

谢谢:(

更新

@Mirco S.的回答解决了我的问题。只需要添加一点点元数据复制逻辑。

Reflect.defineMetadata(
PATH_METADATA,
Reflect.getOwnMetadata(PATH_METADATA, oldPropertyDescriptor.value),
newPropertyDescriptor.value
)

这可能是因为属性描述符中的属性value始终是相同的函数。没有一个通用的深度复制函数可以在所有类型的对象上工作,但对于函数,你可以尝试这样的方法:

// clone the propertyDescriptor to not temper with the original.
const newPropertyDescriptor = {...Object.getOwnPropertyDescriptor(
routesController.prototype,
property
)}
if(typeof newPropertyDescriptor.value === "function") {
const routesControllerFunction = newPropertyDescriptor.value;
// wrap the original function so that Reflect.defineMetadata gets applied to the
// newly created function instead of to the prototype function of FilterController
newPropertyDescriptor.value = (...args: any[]) => routesControllerFunction(...args);
}
Object.defineProperty(
target.prototype,
property,
newPropertyDescriptor
)

如果您需要克隆的函数不止一个,那么您必须添加更多的案例并正确地克隆它们。但是要小心,如果复制这样的函数,则需要调整绑定,并且可能需要添加逻辑来在装饰类中维护适当的this

编辑:关于装饰师的一个小注释。我自己也喜欢装修师,但经过漫长的几年,他们仍处于第二阶段。字体脚本中装饰器的当前实现受到2014年遗留提案的启发,该提案不再符合当前提案。最新的提案是WIP,据我所知,目前还没有针对它们的转译(2021年3月(。在最新的提案中有一些突破性的变化,所以要注意你将来可能需要更新你的装饰器。不过,对于最新的提议,您能够使用遗留装饰器所做的一切都应该是可行的。我们还有可能得到另一个提议。。。。

最新更新