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

Posted

技术标签:

【中文标题】具有复制属性的类方法的元数据在更改源类的元数据时发生更改 - Typescript【英文标题】:Metadata on methods of a class with copied properties changes on changing the metadata of the source class - Typescript 【发布时间】:2021-06-11 03:17:16 【问题描述】:

对不起,冗长的标题。我有一个类 MutateMe 被装饰者 Decorator 传递到一个名为 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
)

【问题讨论】:

【参考方案1】:

这可能是因为属性描述符中的属性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

编辑:关于装饰器的一点旁注。我自己也喜欢装饰器,但经过多年的努力,它们仍处于第 2 阶段。当前在 typescript 中实现装饰器的灵感来自 2014 年的遗留提案,该提案不再符合 current proposal。最新的提案是 WIP,据我所知,没有可用的翻译(2021 年 3 月)。最新提案中有一些重大更改,因此请注意您将来可能需要更新您的装饰器。不过,您可以使用传统装饰器做的所有事情都应该可以使用最新的提案来实现。我们也有可能得到另一个提案......

【讨论】:

瞧!这正是我想要的。看起来getOwnPropertyDescriptor() 只是返回对描述符的引用而不是副本(也许我不熟悉描述符的工作方式)。我不得不添加它以使其适用于我的用例编辑:看起来 cmets 不喜欢代码块。我已经根据要求更新了我的问题。

以上是关于具有复制属性的类方法的元数据在更改源类的元数据时发生更改 - Typescript的主要内容,如果未能解决你的问题,请参考以下文章

Apache Storm 可以用来处理具有一组动态属性的元组吗?

当我为一个类的字段赋值时,添加一些与特定字段关联的元数据?

反射学习

Django:从带有元的抽象类的多重继承

具有自定义元类的类的所有子类共享相同的属性,即使它们不应该

Java面试 | 类的加载机制是什么?