Angular 2 Decorators - 2

Angular 2 Decorators - part 1 文章中,咱們介紹了 TypeScript 中的四種裝飾器。本文的主要目的是介紹 Angular 2 中常見的內置裝飾器。Angular 2 內置裝飾器分類:git

  • 類裝飾器github

    • @Component、@NgModule、@Pipe、@Injectable數組

  • 屬性裝飾器angular2

    • @Input、@Output、@ContentChild、@ContentChildren、@ViewChild、@ViewChildrenapp

  • 方法裝飾器框架

    • @HostListenerionic

  • 參數裝飾器ide

    • @Inject、@Optional、@Self、@SkipSelf、@Host函數

Angular 2 類裝飾器示例:

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

Angular 2 屬性裝飾器示例:

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

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

Angular 2 方法裝飾器示例:

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
  }
}

Angular 2 參數裝飾器示例:

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) { // 與myService: MyService等價
    console.log(myService);
  }
}

下面咱們就着重分析一下最經常使用的類裝飾器 @Component ,其它的裝飾器讀者有興趣的話,能夠參考 Component 的分析思路自行分析。動畫

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

@Component({
  selector: 'my-app', 
  template: `<h1>Hello {{name}}</h1>`, 
})
export class AppComponent  {
  name = 'Angular'; 
}

首先從最簡單的例子入手,咱們都知道採用 TypeScript 開發,爲了保證兼容性最終都會轉換成標準的 ES 5代碼。上面的例子轉成以下的代碼:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  ...
};

define(["require", "exports", "@angular/core"], function (require, exports, core_1) {
    "use strict";
  
    Object.defineProperty(exports, "__esModule", { value: true });
    var AppComponent = (function () {
        function AppComponent() {
            this.name = 'Angular';
        }
        return AppComponent;
    }());
    AppComponent = __decorate([
        core_1.Component({ // (1)
            selector: 'my-app',
            template: "<h1>Hello {{name}}</h1>",
        })
    ], AppComponent);
    exports.AppComponent = AppComponent;
});

經過 Angular 2 Decorators - part 1 文章,咱們知道 TypeScript 類裝飾器的聲明:

declare type ClassDecorator = <TFunction extends Function>(target: TFunction)
  => TFunction | void;

而轉換後 ES5 代碼中 __decorate 函數的方法簽名是 function (decorators, target, key, desc) 。所以咱們能夠推斷,core_1.Component 是一個函數,該函數調用後返回一個 ClassDecorator 。相似於 Angular 2 Decorators - part 1 文章中的 Greeter 裝飾器:

function Greeter(greeting: string) {
  return function(target: Function) {
    target.prototype.greet = function(): void {
      console.log(greeting);
    }
  }
}

@Greeter('您好')
class Greeting {
  constructor() {
    // 內部實現
  }
}

let myGreeting = new Greeting();
myGreeting.greet(); // console output: '您好!';

那咱們來看一下 @angular/core 模塊中導出的 Component 函數:

/**
 * Component decorator and metadata.
 */
export const Component: ComponentDecorator = <ComponentDecorator>makeDecorator(
    'Component', {
      selector: undefined, // 用於定義組件在HTML代碼中匹配的標籤
      inputs: undefined, // 組件的輸入屬性
      outputs: undefined, // 組件的輸出屬性
      host: undefined, // 綁定宿主的屬性、事件等
      exportAs: undefined, // 導出指令,使得能夠在模板中調用
      moduleId: undefined, // 包含該組件模塊的id,它被用於解析模板和樣式的相對路徑 
      providers: undefined, // 設置組件及其子組件能夠用的服務
      viewProviders: undefined, // 設置組件及其子組件(不含ContentChildren)能夠用的服務
      changeDetection: ChangeDetectionStrategy.Default, // 指定組件使用的變化檢測策略
      queries: undefined, // 設置組件的查詢條件
      templateUrl: undefined, // 爲組件指定一個外部模板的URL地址
      template: undefined, // 爲組件指定一個內聯的模板
      styleUrls: undefined, // 爲組件指定一系列用於該組件的樣式表文件
      styles: undefined, // 爲組件指定內聯樣式
      animations: undefined, // 設置組件相關動畫
      encapsulation: undefined, // 設置組件視圖包裝選項
      interpolation: undefined, // 設置默認的插值運算符,默認是"{{"和"}}"
      entryComponents: undefined // 設置須要被提早編譯的組件
    },
    Directive);

讓咱們繼續來看一下 makeDecorator 這個函數:

// @angular/core/src/util/decorators.ts

/**
 * const Component: ComponentDecorator = <ComponentDecorator>makeDecorator(
 *   'Component', {...}, Directive);
 */
function makeDecorator(name, props, parentClass, chainFn) { 
          // name: 'Component', props: {...}, parentClass: Directive
        if (chainFn === void 0) { chainFn = null; }
  
          // 建立Metadata構造函數
        var metaCtor = makeMetadataCtor([props]); 
          // objOrType: { selector: 'my-app', template: "<h1>Hello {{name}}</h1>" }
        function DecoratorFactory(objOrType) { 
          
              // 確保已經引入了Reflect庫
            if (!(Reflect && Reflect.getMetadata)) {
                throw 'reflect-metadata shim is required when using class decorators';
            }
          
              // 判斷this對象是否爲DecoratorFactory的實例,如果則合併metadata信息
            if (this instanceof DecoratorFactory) { 
                metaCtor.call(this, objOrType);
                return this;
            }

            var annotationInstance = new DecoratorFactory(objOrType); 
            var chainAnnotation = typeof this === 'function' && 
                Array.isArray(this.annotations) ? this.annotations : [];
            chainAnnotation.push(annotationInstance);
          
              // 定義類裝飾器,參數即要裝飾的類
            var TypeDecorator = function TypeDecorator(cls) {
                // 首先先獲取裝飾類關聯的annotations信息,若不存在則建立
                // 保存上面建立的annotationInstance實例,並調用保存更新後的annotations信息
                var annotations = Reflect.getOwnMetadata('annotations', cls) || [];
                annotations.push(annotationInstance); 
                Reflect.defineMetadata('annotations', annotations, cls);
                return cls;
            };
          
            TypeDecorator.annotations = chainAnnotation;
            TypeDecorator.Class = Class;
            if (chainFn) chainFn(TypeDecorator);
          
            return TypeDecorator;
        }
        if (parentClass) {
            DecoratorFactory.prototype = Object.create(parentClass.prototype);
        }
        DecoratorFactory.prototype.toString = function () { return ("@" + name); };
        DecoratorFactory.annotationCls = DecoratorFactory;
        return DecoratorFactory;
}

// 生成Metadata構造函數
function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any {
   // args: [{ selector: 'my-app', template: "<h1>Hello {{name}}</h1>" }]
  return function ctor(...args: any[]) {
    props.forEach((prop, i) => {
       // argVal: { selector: 'my-app', template: "<h1>Hello {{name}}</h1>" }
      const argVal = args[i];
      if (Array.isArray(prop)) {
        this[prop[0]] = argVal === undefined ? prop[1] : argVal;
      } else {
        // propName: 'selector' | 'template'
        for (const propName in prop) { 
          this[propName] =
              argVal && argVal.hasOwnProperty(propName) ? 
                argVal[propName] : prop[propName];
        }
      }
    });
  };
}

經過閱讀以上的源碼,咱們發現當調用 makeDecorator('Component', {..}, Directive) 方法時,返回的是

DecoratorFactory 函數,該函數只接收一個參數,當調用該工廠函數時,則返回 TypeDecorator 函數即類裝飾器。回到最先的例子,當咱們調用 core_1.Component({ selector: 'my-app', template: "..." }) 建立的 annotationInstance 實例,內部結構以下:

{
      selector: 'my-app', 
      inputs: undefined, 
      outputs: undefined, 
      host: undefined, 
      exportAs: undefined, 
      moduleId: undefined,  
      providers: undefined, 
      viewProviders: undefined, 
      changeDetection: ChangeDetectionStrategy.Default, 
      queries: undefined, 
      templateUrl: undefined, 
      template: "<h1>Hello {{name}}</h1>",
      styleUrls: undefined, 
      styles: undefined, 
      animations: undefined, 
      encapsulation: undefined, 
      interpolation: undefined, 
      entryComponents: undefined
}

如今咱們來梳理一下整個流程,系統初始化的時候,會調用 makeDecorator('Component', {..}, Directive) 方法,建立 ComponentDecorator 工廠 。咱們編寫的 @Component 組件轉換成 ES 5 代碼後,會使用用戶自定義的 metadata 信息做爲參數,自動調用 ComponentDecorator 工廠函數,該函數內部實現的主要功能就是建立 annotationInstance 對象,最後返回 TypeDecorator 類裝飾器。該類裝飾器會被 __decorate([...], AppComponent) 函數調用,參數 traget 就是咱們要裝飾的類 。

我有話說

  1. 由於一個類能夠應用多個裝飾器,因此 var annotations = Reflect.getOwnMetadata('annotations', cls) || [] 語句中,annotations 的值是數組。在 Angular 2 中,應用多個裝飾器的情形是使用 @Optional 、@Inject()、@Host 等參數裝飾器,描述構造函數中須要注入的依賴對象。

  2. 經過 Reflect.defineMetadata API 定義的 metadata 信息,是保存在 window['__core-js_shared__'] 對象的 metadata 屬性中。感興趣的話,你們能夠直接在 Console 控制檯,輸入 window['__core-js_shared__'] 查看該對象內部保存的信息。

  3. @Component 中 @ 符號的做用是爲了告訴 TypeScript 編譯器,@ 後面的是裝飾器函數或裝飾器工廠,須要特殊處理。假設在 @Component({...}) 中去掉 @ 符號,那麼變成了普通的函數調用,這樣立刻就會報錯,由於咱們並無定義 Component 函數。經過觀察轉換後的代碼,咱們發現 @Component({...}) 被轉換成 core_1.Component ,它就是從 @angular/core 導入的裝飾器函數。

總結

本文介紹了 Angular 2 中最經常使用的 ComponentDecorator 裝飾器,並經過簡單的例子,一步步分析該裝飾器的內部工做流程。不過咱們只介紹了 Angular 2 框架內部如何解析、建立及保存 metadata 信息,還未涉及到組件初始化的過程當中,如何讀取、應用組件對應的 metadata 信息。另外在後續的 Angular 2 DI 文章中,咱們還會繼續分析其它裝飾器的工做原理。

相關文章
相關標籤/搜索