深入研究 Angular 框架中的装饰

使用Angular(Angular 2 及以上版本)开发程序时,装饰是一个核心概念。还有一个正式的TC39 提案,目前处于阶段2中,该提案期望装饰器能够很快成为JavaScript 的核心语言功能。

回到Angular ,Angular 的内部代码广泛使用了装饰器,本篇文章中我们将学习不同类型的装饰器和它们的源码并且了解它们是如何工作的。

我第一次接触到TypeScript 和装饰器的时候,我不知道我为什么需要它们,但是当你稍微往深处发掘的时候你才能了解到了创建装饰器的好处(不仅是在Angular 中)。

在AngularJS 中没有使用装饰器,而是使用了不同的注册方法——例如用 .component() 方法定义一个组件。那为什么Angular 选择使用装饰器呢?让我们开始探索吧!

目录

Angular 装饰器

类装饰器

属性装饰器

方法装饰器

参数装饰器

创建装饰器

装饰器函数

向装饰器传输数据

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:'

Woo a component!
'})exportclassExampleComponent{constructor() {console.log('Hey I am a component!');  }}@NgModule({  imports: [],  declarations: []})exportclassExampleModule{constructor() {console.log('Hey I am a module!');  }}

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

属性装饰器

这些可能是第二个最常见的装饰器了。他们允许我们在我们的类内部装饰特定的属性 - 一个非常强大的机制。

我们来看看@Input()。想象一下,我们有一个属性,我们想要一个输入绑定。

如果没有装饰器,我们必须在我们的类中定义这个属性,以便TypeScript知道它,然后在其他地方告诉Angular我们有一个属性,我们希望有一个输入方法。

使用装饰器,我们可以简单地将@Input()装饰器放在属性的上方 - Angular的编译器会自动从属性名称创建一个输入绑定并将它们链接起来。

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

@Component({

selector: 'example-component',

template: '

Woo a component!
' }) export class ExampleComponent {

@Input()

exampleProperty: string;

}

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

[exampleProperty]="exampleData"> 

属性装饰器会在ExampleComponentdefinition内发生“魔术”。

在AngularJS 1.x(我打算在这里也使用TypeScript,只是为了声明一个类的属性),我们有一个不同的机制,使用scope或bindToController与指令,并在新的组件方法中bindings:

const exampleComponent = {

bindings: {

exampleProperty: '<'   },

template: `

Woo a component!

`,

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: '

Woo a component!
' }) export class ExampleComponent {

@HostListener('click', ['$event'])

onHostClick(event: Event) {

// clicked, `event` available   }

}

装饰器参数

装饰器的参数十分有趣。 在将基元注入到构造函数中时,您可能遇到过这些问题,您需要手动通知Angular注入特定的提供程序。

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

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

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

@Component({

selector: 'example-component',

template: '

Woo a component!
' }) 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: '

Woo a component!
' }) 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!');

}

}

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

将数据传递给装饰器

当我们在Angular中使用装饰器时,我们传递一些特定于装饰器的配置。

例如,当我们使用@Component时,我们通过一个对象,并使用@HostListener,通过一个字符串作为第一个参数(事件名称,比如'click')和可选的字符串数组(如$事件)被传递到装饰的方法里。

让我们稍微修改我们上面的控制台代码来展示如何使用Angular装饰器。

@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创建这些装饰器的详细代码,因为我们只需要在更高的思维层面上理解它们就就可以了。

存储元数据

装饰器的要点是存储关于我们已经创建过的类,方法或属性的元数据。例如,当你配置一个组件时,你提供了这个类的元数据,告诉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: '

Woo a component!
' }) 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: '

Woo a component!
',

styleUrls: ['example.component.scss'],

styles: undefined,

animations: undefined,

encapsulation: undefined,

interpolation: undefined,

entryComponents: undefined }

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

装饰器

如果第一次在类上使用装饰器,它将创建一个新的数组,并将注释实例推入其中。 如果这不是在类上使用的第一个装饰器,则将其推送到现有的注释数组中。 这允许装饰器被链接在一起并且全部存储在一个地方。

例如,在Angular中,你可以这么写一个类中的属性:

export class TestComponent {

@Input()

@HostListener('click', ['$event'])

onClick: Function;

}

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

如何使用装饰器

所以我们现在知道Angular如何使用以及为什么使用装饰器,但是他们如何实际应用于一个类?

如前所述,装饰器本身并不是JavaScript本身 - 目前TypeScript为我们提供了这一功能。 这意味着我们可以检查编译的代码,看看我们使用装饰器时会发生什么。

以下一个标准的ES6类 -

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能够存储类的元数据,并同时简化我们的工作流程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • core package 概要:Core是所有其他包的基础包.它提供了大部分功能包括metadata,templa...
    LOVE小狼阅读 2,540评论 0 3
  • 前端开发面试题 <a name='preface'>前言</a> 只看问题点这里 看全部问题和答案点这里 本文由我...
    自you是敏感词阅读 746评论 0 3
  • 一句我信任你的人品,我瞬间泪崩,2018在风雨中奔跑。只为信任我的人,和值得被信任的人。
    静赏花开_5e76阅读 246评论 0 0
  • 又是一年清明,白简又来到了桃花林中,来到了这座坟前。 放下了祭品,白简环顾四周,只看到满目娇艳欲滴的桃花,却不见从...
    一只糯米粽子阅读 292评论 0 0