【问题标题】:Add a decorator to a new property with a class decorator使用类装饰器将装饰器添加到新属性
【发布时间】:2019-01-17 13:49:17
【问题描述】:

我想知道的几乎都在标题中。 我想知道如何使用类装饰器添加具有自己的装饰器的新属性。

我想创建一个类装饰器 Api(string[]) 来公开类装饰器中列出的类方法。 为此,我想从 Angular 发出带有 EventEmitter 的事件,为此我必须将 @Output() 装饰器添加到新属性中。

可以吗?

以下示例:我只有 MyClass 和方法过程,打开,关闭。 我想创建装饰器来公开我想要的任何方法(在这里,打开和关闭)。我想象了一个类装饰器,它添加了api 属性和一个方法装饰器来公开一个方法,也许吧?

class MyClass {
  @Output() api = new EventEmitter();

  $exposedMethods: object = {};

  constructor() {
    this.$exposedMethods = {
      open: this.open.bind(this),
      close: this.close.bind(this)
    };
    this.api.emit(this.$exposedMethods);
  }

  process() {

  }

  open() {
    // stuff...
  }

  close() {
    // stuff...
  }
}

【问题讨论】:

  • 请提供一个minimal reproducible example 你想要的没有装饰器,我们会用装饰器来改变它,做完全相同的事情。
  • 完成,抱歉缺少示例

标签: angular typescript


【解决方案1】:

好的,所以要振作起来,因为这是一个很难掌握的概念

这里有现场演示:

https://stackblitz.com/edit/angular-2kxtzs?file=src%2Fapp%2Fhello.component.ts

代码:

import { Component, Input, Output, EventEmitter } from '@angular/core';

const Expose: (methods: string[]) => ClassDecorator = (methods) => {

  return component => {

    for (const method of methods) {
      const eventEmitterName = `${method}Emitter`;
      component.prototype[eventEmitterName] = new EventEmitter();

      const outputFactory = Output(method);

      const orgFn = component.prototype[method];

      component.prototype[method] = (...args) => {
        orgFn(...args);
        component.prototype[eventEmitterName].emit();
      }

      outputFactory(component.prototype, eventEmitterName);
    }
  }
}

@Component({
  selector: 'hello',
  template: `<button (click)="open()">Emit an open event</button>`,
  styles: [`h1 { font-family: Lato; }`]
})
@Expose(['open'])
export class HelloComponent {

  @Input() name: string;

  open() {
    console.log('Clicked on the button, now emitting an event');
  }

  ngOnInit() {}
}

类装饰器是函数。
在您的情况下,这是一个类装饰器工厂:您提供参数,它应该返回一个类装饰器。这是您可以看到的签名:

const Expose: (methods: string[]) => ClassDecorator = (methods) => { ... }

它声明Expose 是一个返回类装饰器的工厂。您的工厂接受方法列表作为参数。

现在,这个工厂需要返回一个类装饰器。类装饰器是一个将组件本身作为唯一参数的函数。这条线

return component => { ... }

它返回一个符合ClassDecorator签名的函数。

之后,您需要重写每个方法。所以你会用一个简单的循环来循环它们。

在循环中,我们将创建一个新的事件发射器。为简单起见,我们将使用名称[method]Emitter。所以我们从创建圣名开始:

const eventEmitterName = `${method}Emitter`;

完成后,我们将其绑定到组件的原型:

component.prototype[eventEmitterName] = new EventEmitter();

你现在有了你的事件发射器。

之后,您需要将输出装饰器绑定到它。如果你按照第一步,你就会明白Output其实也是一个工厂。这意味着它返回一个MethodDecorator 函数,其签名是

(component, methodKey) => { ... }

(还有第三个参数叫做描述符,但你不需要它,所以我将忽略它)。

一旦知道这一点,我们就为我们的方法获取工厂结果:

const outputFactory = Output(method);

这将创建一个以您的方法命名的输出(此处为open)。

一旦完成,我们将覆盖给定的方法以在其处理完成时发出事件。

这是基本的 JS 函数覆盖:

const orgFn = component.prototype[method];

component.prototype[method] = (...args) => {
  orgFn(...args);
  component.prototype[eventEmitterName].emit();
}

在最后一行,我们通过之前创建的事件发射器发射事件。

现在,我们剩下要做的就是将此事件发射器绑定到我们的组件。为此,我们只需调用输出工厂创建的方法装饰器。

outputFactory(component.prototype, eventEmitterName);

现在,您的装饰器已经完成并且可以正常工作了。正如您在 stackblitz 上看到的,open 函数中的代码正在运行,然后一旦运行,应用程序组件模板中 (open) 输出的代码就会运行。

还有 Voilààààà !

【讨论】:

  • 当然,我没有编写你想要的代码,但它背后的原理(我不是在这里为你编写代码):如果你想返回你的子组件的函数,你将不得不稍微改变你的装饰器。没什么太难的,别担心,我相信你明白了。
  • 什么解释!比发布解决方案更好,我现在更了解实际装饰者在做什么,谢谢先生。的确,在你的帮助下,我现在可以自己做这件事了。希望我能不止一次地支持您的解决方案。再次感谢您!
  • 我很高兴你能理解它,祝你的项目好运 :) 如果你觉得这回答了你的问题,请随时将其标记为已解决和/或支持我的答案以将其放在首位页面!
  • 我确实做到了!再次感谢您!
  • 再次嗨,我以为我已经完成了这个,但是......不。我不明白为什么。当我在同一页面上有多个相同的组件时,仅针对最后一个引发事件。我不明白为什么':(
猜你喜欢
  • 2017-11-19
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
  • 2019-11-02
  • 1970-01-01
  • 1970-01-01
  • 2017-07-30
  • 1970-01-01
相关资源
最近更新 更多