(更新時間 - 2017-03-20 9:00)json
在 Angular 2 中,Inject 是參數裝飾器,用來在類的構造函數中描述非 Type 類型的參數對象。segmentfault
Angular 2 中 Type 類型:api
// Type類型 - @angular/core/src/type.ts export const Type = Function; export function isType(v: any): v is Type<any> { return typeof v === 'function'; } export interface Type<T> extends Function { new (...args: any[]): T; }
Angular 2 中經常使用的非 Type 類型 Token:字符串、OpaqueToken對象、InjectionToken對象等。數組
/* * 用於建立OpaqueToken實例 * export const CONFIG = new OpaqueToken('config'); */ export class OpaqueToken { constructor(protected _desc: string) {} toString(): string { return `Token ${this._desc}`; } } /* * 用於建立InjectionToken實例,使用泛型描述該Token所關聯的依賴對象的類型 * const API_URL = new InjectionToken<string>('apiUrl'); */ export class InjectionToken<T> extends OpaqueToken { private _differentiate_from_OpaqueToken_structurally: any; constructor(desc: string) { super(desc); } toString(): string { return `InjectionToken ${this._desc}`; } }
(備註:各類 Token 類型的區別,請參照 Angular 2 OpaqueToken & InjectionToken)app
config.ts函數
export const CONFIG = new OpaqueToken('config');
app.service.tsthis
import { Injectable } from '@angular/core'; @Injectable() export class AppService { constructor() { } }
app.component.tsspa
import { Component, Inject, ViewChild, HostListener, ElementRef } from '@angular/core'; import { CONFIG } from './config'; import { AppService } from './app.service'; @Component({ selector: 'my-app', template: `<h1 #greet> Hello {{ name }} </h1>`, }) export class AppComponent { name = 'Angular'; @ViewChild('greet') private greetDiv: ElementRef; @HostListener('click', ['$event']) onClick($event: any) { console.dir($event); } constructor(public appService: AppService, @Inject(CONFIG) config: any) { } }
編譯後的 ES5 代碼片斷:code
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {...}; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var AppComponent = (function () { // 構造函數 function AppComponent(appService, config) { this.appService = appService; this.name = 'Angular'; } AppComponent = __decorate([ core_1.Component({ // 調用ComponentDecoratorFactory返回TypeDecorator selector: 'my-app', template: "<h1 #greet> Hello {{ name }} </h1>", }), // 調用ParamDecoratorFactory返回ParamDecorator __param(1, core_1.Inject(config_1.CONFIG)), // 保存構造函數參數的類型 __metadata('design:paramtypes', [app_service_1.AppService, Object]) ], AppComponent); return AppComponent; }()); exports.AppComponent = AppComponent;
Inject、InjectDecorator 接口及 Inject 函數:component
// Inject接口定義 export interface Inject { token: any; } // InjectDecorator接口定義 export interface InjectDecorator { (token: any): any; new (token: any): Inject; // 構造函數的簽名 } // Inject裝飾器:即示例中轉成ES5代碼後的 core_1.Inject 對象 - core_1.Inject(config_1.CONFIG) export const Inject: InjectDecorator = makeParamDecorator('Inject', [['token', undefined]]);
makeParamDecorator函數片斷:
/* * 建立ParamDecorator工廠 * * 調用 makeParamDecorator('Inject', [['token', undefined]])後返回ParamDecoratorFactory */ function makeParamDecorator(name, props, parentClass) { // name: 'Inject', props: [['token', undefined]] // 建立Metadata構造函數 var metaCtor = makeMetadataCtor(props); // __param(1, core_1.Inject(config_1.CONFIG)) function ParamDecoratorFactory() { // 解析參數並建立annotationInstance實例 var args = []; // arguments: {0: CONFIG} for (var _i = 0; _i < arguments.length; _i++) { args[_i - 0] = arguments[_i]; } if (this instanceof ParamDecoratorFactory) { // args: [CONFIG] metaCtor.apply(this, args); return this; } ... return ParamDecorator; function ParamDecorator(cls, unusedKey, index) { // 獲取類已經定義的metadata信息 var parameters = Reflect.getOwnMetadata('parameters', cls) || []; while (parameters.length <= index) { parameters.push(null); } // parameters是一個二維數組,由於支持同時應用多個裝飾器 // eg: @Inject(CONFIG) @Optional() @SkipSelf() config: any parameters[index] = parameters[index] || []; parameters[index].push(annotationInstance); Reflect.defineMetadata('parameters', parameters, cls); return cls; } var _a; } return ParamDecoratorFactory; }
makeMetadataCtor 函數:
// 生成Metadata構造函數: var metaCtor = makeMetadataCtor(props); // props: [['token', undefined]] function makeMetadataCtor(props) { return function ctor() { /* * metaCtor.apply(this, args); */ var _this = this; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i - 0] = arguments[_i]; } props.forEach(function (prop, i) { // prop: ['token', undefined] var argVal = args[i]; if (Array.isArray(prop)) { // prop: ['token', undefined] // prop[0]: token, argVal: CONFIG - {_desc: "config"} _this[prop[0]] = argVal === undefined ? prop[1] : argVal; } else { for (var propName in prop) { _this[propName] = argVal && argVal.hasOwnProperty(propName) ? argVal[propName] : prop[propName]; } } }); }; }
接下來咱們能夠在控制檯輸入 window['__core-js_shared__'] ,查看經過 Reflect API 保存後的metadata信息
最後咱們來了解一下,Angular 如何獲取 AppComponent 構造函數中,經過 @Inject 裝飾器設置的 metadata信息。
// @angular/core/src/reflection/reflection_capabilities.ts export class ReflectionCapabilities implements PlatformReflectionCapabilities { // 獲取ParamDecorator函數中經過Reflect.defineMetadata('parameters', parameters, cls) // 保存的metadata信息 parameters(type: Type<any>): any[][] { if (!isType(type)) { return []; } const parentCtor = getParentCtor(type); let parameters = this._ownParameters(type, parentCtor); if (!parameters && parentCtor !== Object) { parameters = this.parameters(parentCtor); } return parameters || []; } } private _ownParameters(type: Type<any>, parentCtor: any): any[][] { /* * constructor( * public appService: AppService, * @Inject(CONFIG) config: any) { * } */ if (this._reflect != null && this._reflect.getOwnMetadata != null) { // @Inject(CONFIG) config: any -> 'parameters' const paramAnnotations = this._reflect.getOwnMetadata('parameters', type); // appService: AppService -> 'design:paramtypes' const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type); if (paramTypes || paramAnnotations) { return this._zipTypesAndAnnotations(paramTypes, paramAnnotations); } } }
1.爲何在構造函數中,非 Type 類型的參數只能用 @Inject(Something) 的方式注入 ?
由於只有是 Type 類型的對象,纔會被 TypeScript 編譯器編譯。具體參照下圖:
2.爲何 TypeScript 會自動保存 metadata 信息 ?
由於咱們在 tsconfig.json 文件中,進行以下配置:
{ "compilerOptions": { ..., "emitDecoratorMetadata": true } }
3.AppService 中 @Injectable() 是必須的麼 ?
若是 AppService 不依賴於其餘對象,是能夠不用使用 Injectable 類裝飾器。當 AppService 須要在構造函數中注入依賴對象,就須要使用 Injectable 類裝飾器。比較推薦的作法無論是否有依賴對象,service 中都使用 Injectable 類裝飾器。
4.Reflect 對象還有哪些方法 ?
Reflect .defineMetadata(metadataKey, metadataValue, target, propertyKey?) -> void .getMetadata(metadataKey, target, propertyKey?) -> var .getOwnMetadata(metadataKey, target, propertyKey?) -> var .hasMetadata(metadataKey, target, propertyKey?) -> bool .hasOwnMetadata(metadataKey, target, propertyKey?) -> bool .deleteMetadata(metadataKey, target, propertyKey?) -> bool .getMetadataKeys(target, propertyKey?) -> array .getOwnMetadataKeys(target, propertyKey?) -> array .metadata(metadataKey, metadataValue) -> decorator(target, targetKey?) -> void
Reflect API 使用示例
var O = {}; Reflect.defineMetadata('foo', 'bar', O); Reflect.ownKeys(O); // => [] Reflect.getOwnMetadataKeys(O); // => ['foo'] Reflect.getOwnMetadata('foo', O); // => 'bar'
5.使用 Reflect API 有什麼好處 ?
使用 Reflect API 咱們可以方便的對類相關的 metadata 信息進行保存和讀取
Reflect API 把類相關的 metadata 信息保存在 window['__core-js_shared__'] 對象中,避免對類形成污染。
6.在構造函數中,Type 類型的參數能用 @Inject(Type) 的方式注入麼?
Type 類型的參數也能使用 @Inject(Type) 的方式注入,具體以下:
constructor(@Inject(Http) private http) { }
一樣也能夠使用如下方式:
constructor(@Inject(Http) private http: Http) { }
第一種方式雖然能夠正常編譯,但 IDE 會有以下的提示信息:
[ts] Parameter 'http' implicitly has an 'any' type.
第二種方式,雖然 Angular 內部會合並 design:paramtypes 與 parameters 內的 metadata 信息,但本人以爲是有點冗餘了。 總而言之,若果是 Type 類型的參數,推薦使用下面的方式:
constructor(private http: Http) { }
本文經過一個示例,一步步分析 Inject 裝飾器的做用及內部實現原理,此外咱們還解釋了在構造函數中爲何非 Type 類型的參數只能經過 @Inject(Something) 的方式注入及 Injectable裝飾器的使用場景,最後咱們還簡單介紹了Reflect API。但願經過這篇文章,能讓讀者更好地理解 Inject
裝飾器。