[聚合文章] 深入研究 Angular 框架中的装饰

Angular 2017-12-14 13 阅读

使用 Angular(Angular 2 及以上版本)开发程序时,装饰是一个核心概念。还有一个正式的 TC39 提案,目前处于阶段 2 中,该提案期望装饰器能够很快成为 JavaScript 的核心语言功能。 回到 Angular ,Angular 的内部代码广泛使用了装饰器,本篇文章中我们将学习不同类型的装饰器和它们的源码并且了解它们是如何工作的。 我第一次接触到 TypeScript 和装饰器的时候,我不知道我为什么需要它们,但是当你稍微往深处发掘的时候你才能了解到了创建装饰器的好处(不仅是在 Angular 中)。 在 AngularJS 中没有使用装饰器,而是使用了不同的注册方法——例如用 .component() 方法定义一个组件。那为什么 Angular 选择使用装饰器呢?让我们开始探索吧!

Angular 装饰器

在我们创建装饰器和了解为什么 Angular 使用它们之前,我们先看看 Angular 提供的不同类型的装饰器。主要右四个类型: 类装饰器,例如@Component @NgModule。 属性内部的属性装饰器,例如@Input  和  @Output。 方法内部的方法装饰器,例如@HostListener。 类构造函数中参数的参数装饰器,例如   @Inject 每个装饰器都有一个独特的作用,让我们看几个示例来扩展上面的列表。

类装饰器 Angular 提供了几个类装饰器。这些是我们用来表示类的意图时使用的顶级装饰器。例如,这些装饰器允许我们告诉 Angluar 一个特定的类是一个组件或者是一个组件。装饰器允许我们定义类的意图而不用在类的内部写实际的代码。 一个类中的@Component 和  @NgModule 实例:
import { NgModule, Component } from '@angular/core';

@Component({
  selector: 'example-component',
  template: '<div>Woo a component!</div>'
})
export class ExampleComponent {
  constructor() {
    console.log('Hey I am a component!');
  }
}

@NgModule({
  imports: [],
  declarations: []
})
export class ExampleModule {
  constructor() {
    console.log('Hey I am a module!');
  }
}

请注意,不管这两个类本身是如何的它们实际上是相同的。在类中不需要任何代码去告知 Angluar 这个类是 component 还是 module 。我们需要做的只是修饰这个类,余下的工作交给 Angular 就可以了。

属性装饰器
import { Component, Input } from '@angular/core';

@Component({
  selector: 'example-component',
  template: '<div>Woo a component!</div>' }) export class ExampleComponent {
  @Input()
  exampleProperty: string;
}

然后我们通过一个组件属性绑定来传递输入绑定:

<example-component
  [exampleProperty]="exampleData"> </example-component>

属性装饰器会在 ExampleComponentdefinition 内发生“魔术”。 在 AngularJS 1.x(我打算在这里也使用 TypeScript,只是为了声明一个类的属性),我们有一个不同的机制,使用 scope 或 bindToController 与指令,并在新的组件方法中 bindings:

const exampleComponent = {
  bindings: {
    exampleProperty: '<'   },
  template: `
    <div>Woo a component!</div>
  `,
  controller: class ExampleComponent {
    exampleProperty: string;
    $onInit() {
      // access this.exampleProperty     }
  }
};

angular
  .module('app')
  .component('exampleComponent', exampleComponent);

您可以在上面看到,如果我们扩展,重构或更改组件的 API 绑定和类内的属性名称,我们有两个单独的属性可以维护。然而,在 Angular 中,有一个属性 exampleProperty 被装饰,随着我们的代码库的增长,这个属性更容易更改,维护和追踪。

装饰器 方法 装饰器方法与装饰器属性非常相似,但是用来写方法的。 这可以用来在我们的类中修饰特定的方法。 一个很好的例子是@HostListener。 这使我们可以告诉 Angular,当我们的主程序发生事件时,我们希望用事件调用装饰的方法。
import { Component, HostListener } from '@angular/core';

@Component({
  selector: 'example-component',
  template: '<div>Woo a component!</div>' }) export class ExampleComponent {
  @HostListener('click', ['$event'])
  onHostClick(event: Event) {
    // clicked, `event` available   }
}
装饰器 参数

深入挖掘依赖注入(DI),令牌,@Inject 和@Injectable,可以看看我以前的文章。

参数装饰器允许我们在我们的类构造函数中修饰参数。 这个例子是@Inject,它让我们告诉 Angular 我们想要什么参数来启动:

import { Component, Inject } from '@angular/core'; import { MyService } from './my-service';

@Component({
  selector: 'example-component',
  template: '<div>Woo a component!</div>' }) export class ExampleComponent {
  constructor(@Inject(MyService) myService) {
    console.log(myService); // MyService   }
}

由于 TypeScript 公开接口允许给我们使用元数据,我们实际上并不需要这么做。 我们可以让 TypeScript 和 Angular 通过指定要注入的作为参数类型来完成我们的辛苦工作:

import { Component } from '@angular/core'; import { MyService } from './my-service';

@Component({
  selector: 'example-component',
  template: '<div>Woo a component!</div>' }) export class ExampleComponent {
  constructor(myService: MyService) {
    console.log(myService); // MyService   }
}

现在我们已经介绍了我们可以使用的装饰器类型,让我们深入了解他们正在做的事情 – 以及为什么我们需要它们。

创建一个装饰器 如果我们了解一个装饰器实际上正在做什么,然后再研究 Angular 如何使用它们,它会使事情变得更容易。要做到这一点,我们可以创建一个快速的装饰器示例。  装饰器函数
function Console(target) {
  console.log('Our decorated class', target);
}

在这里,我们已经创建了控制台(Angular 通常使用大写命名约定),并指定一个名为目标的参数。目标参数实际上是我们装饰的类,这意味着我们现在可以用装饰器来装饰任何类,并在控制台中看到它的输出结果:

@Console class ExampleClass {
  constructor() {
    console.log('Yo!');
  }
}

想要看到实际操作?看看现场演示。

将数据传递给装饰器
@Console('Hey!') class ExampleClass {
  constructor() {
    console.log('Yo!');
  }
}

如果我们现在运行这个代码,我们只会得到’Hey!’。这是因为我们的装饰器没有返回给予类的函数。 @Console(’Hey!’)的输出是无效的。 我们需要调整我们的控制台代码的装饰器,以返回给予类的函数闭包。这样我们都可以从装饰器(在我们的例子中是字符串 Hey!)以及类中获得一个值:

function Console(message) {
  // access the "metadata" message   console.log(message);
  // return a function closure, which   // is passed the class as `target`   return function (target) {
    console.log('Our decorated class', target);
  }
}

@Console('Hey!') class ExampleClass {
  constructor() {
    console.log('Yo!');
  }
} // console output: 'Hey!' // console output: 'Our decorated class', class ExampleClass{}...

你可以看到这里的变化。 这是 Angular 装饰器工作的基础。他们首先获取一个配置值,然后接收类/方法/属性来应用装饰。现在我们对装饰器的功能有了一个简单的了解,我们将介绍 Angular 如何创建并使用它自己的装饰器。

装饰器实际上做什么 每种类型的装饰器共享相同的核心功能。 从纯粹的装饰角度来看,@Component 和@Directive 都以相同的方式工作,就像@Input 和@Output 一样。 Angular 通过使用每种类型的装饰器的工厂方法来实现这一点。 让我们来看看 Angular 中最常见的装饰器@Component。 我们不打算用 Angular 创建这些装饰器的详细代码,因为我们只需要在更高的思维层面上理解它们就就可以了。  存储元数据
{
  selector: undefined,
  inputs: undefined,
  outputs: undefined,
  host: undefined,
  exportAs: undefined,
  moduleId: undefined,
  providers: undefined,
  viewProviders: undefined,
  changeDetection: ChangeDetectionStrategy.Default,
  queries: undefined,
  templateUrl: undefined,
  template: undefined,
  styleUrls: undefined,
  styles: undefined,
  animations: undefined,
  encapsulation: undefined,
  interpolation: undefined,
  entryComponents: undefined }

这里有很多不同的选项,你会注意到只有一个有一个默认值 – changeDetection。这是在创建装饰器时指定的,所以无论何时创建组件,我们都不需要添加它。您可能已经应用这一行代码来修改更改策略:

changeDetection: ChangeDetectionStrategy.OnPush

注释实例在使用装饰器时创建。这会将该装饰器的默认配置(例如上面看到的对象)与您指定的配置合并在一起,例如:

import { NgModule, Component } from '@angular/core';

@Component({
  selector: 'example-component',
  styleUrls: ['example.component.scss'],
  template: '<div>Woo a component!</div>' }) export class ExampleComponent {
  constructor() {
    console.log('Hey I am a component!');
  }
}

这将创建一个具有以下属性的注释实例:

{
  selector: 'example-component',
  inputs: undefined,
  outputs: undefined,
  host: undefined,
  exportAs: undefined,
  moduleId: undefined,
  providers: undefined,
  viewProviders: undefined,
  changeDetection: ChangeDetectionStrategy.Default,
  queries: undefined,
  templateUrl: undefined,
  template: '<div>Woo a component!</div>',
  styleUrls: ['example.component.scss'],
  styles: undefined,
  animations: undefined,
  encapsulation: undefined,
  interpolation: undefined,
  entryComponents: undefined }

一旦这个注解实例被创建,它就会被存储,以便 Angular 可以访问它。

装饰器
export class TestComponent {
  @Input()
  @HostListener('click', ['$event'])
  onClick: Function;
}

与此同时,Angular 还可以使用反射 API(通常使用反射元数据进行填充)来存储这些注释,并将该类用作数组。 这意味着它可以稍后通过指向该类来获取特定类的所有注释。

如何使用装饰器
class ExampleClass {
  constructor() {
    console.log('Yo!');
  }
}

然后 TypeScript 把它转换为一个函数:

var ExampleClass = (function () {
  function ExampleClass() {
    console.log('Yo!');
  }
  return ExampleClass;
}());

现在,如果我们加入装饰器装饰我们的类,我们可以看到实际应用的装饰器。

@ConsoleGroup('ExampleClass') class ExampleClass {
  constructor() {
    console.log('Yo!');
  }
}

然后 TypeScript 输出:

var ExampleClass = (function () {
  function ExampleClass() {
    console.log('Yo!');
  }
  return ExampleClass;
}());
ExampleClass = __decorate([
  ConsoleGroup('ExampleClass')
], ExampleClass);

这给了我们一些关于我们的装饰器如何应用的实际上下文。 __decorate 调用是一个辅助函数,可以在编译好的文件顶部输出。 所有这一切能将装饰器应用到我们的类中(使用 ExampleClass 作为参数来调用 ConsoleGroup(’ExampleClass’))。  总结 揭秘装饰者是理解更多 Angular“魔法”和如何使用它们的其中一小步。 他们让 Angular 能够存储类的元数据,并同时简化我们的工作流程。

英文原文: A deep dive on Angular decorators

参与翻译  (2 人) : 凉凉_ , rever4433

转自 https://www.oschina.net/translate/angular-decorators

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。