深入研究 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閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,824評論 18 139
  • core package 概要:Core是所有其他包的基礎包.它提供了大部分功能包括metadata,templa...
    LOVE小狼閱讀 2,625評論 0 3
  • 前端開發面試題 <a name='preface'>前言</a> 只看問題點這里 看全部問題和答案點這里 本文由我...
    自you是敏感詞閱讀 783評論 0 3
  • 一句我信任你的人品,我瞬間淚崩,2018在風雨中奔跑。只為信任我的人,和值得被信任的人。
    靜賞花開_5e76閱讀 249評論 0 0
  • 又是一年清明,白簡又來到了桃花林中,來到了這座墳前。 放下了祭品,白簡環顧四周,只看到滿目嬌艷欲滴的桃花,卻不見從...
    一只糯米粽子閱讀 334評論 0 0