微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决具有复制属性的类的方法的元数据在更改源类的元数据时发生变化 - Typescript

抱歉标题太冗长。我有一个MutateMe一个装饰器 FilterFactory 传入一个名为 Decorator 的工厂。

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。