您好,登錄后才能下訂單哦!
本篇內容主要講解“Angular中的元數據和裝飾器怎么應用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Angular中的元數據和裝飾器怎么應用”吧!
作為“為大型前端項目”而設計的前端框架,Angular 其實有許多值得參考和學習的設計,本系列主要用于研究這些設計和功能的實現原理。本文主要圍繞 Angular 中隨處可見的元數據,來進行介紹。
裝飾器是使用 Angular 進行開發時的核心概念。在 Angular 中,裝飾器用于為類或屬性附加元數據,來讓自己知道那些類或屬性的含義,以及該如何處理它們。
不管是裝飾器還是元數據,都不是由 Angular 提出的概念。因此,我們先來簡單了解一下。
元數據(Metadata)
在通用的概念中,元數據是描述用戶數據的數據。它總結了有關數據的基本信息,可以使查找和使用特定數據實例更加容易。例如,作者,創建日期,修改日期和文件大小是非常基本的文檔元數據的示例。
在用于類的場景下,元數據用于裝飾類,來描述類的定義和行為,以便可以配置類的預期行為。
裝飾器(Decorator)
裝飾器是 JavaScript 的一種語言特性,是一項位于階段 2(stage 2)的試驗特性。
裝飾器是定義期間在類,類元素或其他 JavaScript 語法形式上調用的函數。
裝飾器具有三個主要功能:
可以用具有相同語義的匹配值替換正在修飾的值。(例如,裝飾器可以將方法替換為另一種方法,將一個字段替換為另一個字段,將一個類替換為另一個類,等等)。
可以將元數據與正在修飾的值相關聯;可以從外部讀取此元數據,并將其用于元編程和自我檢查。
可以通過元數據提供對正在修飾的值的訪問。對于公共值,他們可以通過值名稱來實現;對于私有值,它們接收訪問器函數,然后可以選擇共享它們。
本質上,裝飾器可用于對值進行元編程和向其添加功能,而無需從根本上改變其外部行為。
更多的內容,可以參考 tc39/proposal-decorators 提案。
我們在開發 Angular 應用時,不管是組件、指令,還是服務、模塊等,都需要通過裝飾器來進行定義和開發。裝飾器會出現在類定義的緊前方,用來聲明該類具有指定的類型,并且提供適合該類型的元數據。
比如,我們可以用下列裝飾器來聲明 Angular 的類:@Component()
、@Directive()
、@Pipe()
、@Injectable()
、@NgModule()
。
使用裝飾器和元數據來改變類的行為
以@Component()
為例,該裝飾器的作用包括:
將類標記為 Angular 組件。
提供可配置的元數據,用來確定應在運行時如何處理、實例化和使用該組件。
關于@Component()
該如何使用可以參考,這里不多介紹。我們來看看這個裝飾器的定義:
// 提供 Angular 組件的配置元數據接口定義 // Angular 中,組件是指令的子集,始終與模板相關聯 export interface Component extends Directive { // changeDetection 用于此組件的變更檢測策略 // 實例化組件時,Angular 將創建一個更改檢測器,該更改檢測器負責傳播組件的綁定。 changeDetection?: ChangeDetectionStrategy; // 定義對其視圖 DOM 子對象可見的可注入對象的集合 viewProviders?: Provider[]; // 包含組件的模塊的模塊ID,該組件必須能夠解析模板和樣式的相對 URL moduleId?: string; ... // 模板和 CSS 樣式的封裝策略 encapsulation?: ViewEncapsulation; // 覆蓋默認的插值起始和終止定界符(`{{`和`}}`) interpolation?: [string, string]; } // 組件裝飾器和元數據 export const Component: ComponentDecorator = makeDecorator( 'Component', // 使用默認的 CheckAlways 策略,在該策略中,更改檢測是自動進行的,直到明確停用為止。 (c: Component = {}) => ({changeDetection: ChangeDetectionStrategy.Default, ...c}), Directive, undefined, (type: Type<any>, meta: Component) => SWITCH_COMPILE_COMPONENT(type, meta));
以上便是組件裝飾、組件元數據的定義,我們來看看裝飾器的創建過程。
裝飾器的創建過程
我們可以從源碼中找到,組件和指令的裝飾器都會通過makeDecorator()
來產生:
export function makeDecorator<T>( name: string, props?: (...args: any[]) => any, parentClass?: any, // 裝飾器名字和屬性 additionalProcessing?: (type: Type<T>) => void, typeFn?: (type: Type<T>, ...args: any[]) => void): {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} { // noSideEffects 用于確認閉包編譯器包裝的函數沒有副作用 return noSideEffects(() => { const metaCtor = makeMetadataCtor(props); // 裝飾器工廠 function DecoratorFactory( this: unknown|typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any { if (this instanceof DecoratorFactory) { // 賦值元數據 metaCtor.call(this, ...args); return this as typeof DecoratorFactory; } // 創建裝飾器工廠 const annotationInstance = new (DecoratorFactory as any)(...args); return function TypeDecorator(cls: Type<T>) { // 編譯類 if (typeFn) typeFn(cls, ...args); // 使用 Object.defineProperty 很重要,因為它會創建不可枚舉的屬性,從而防止該屬性在子類化過程中被復制。 const annotations = cls.hasOwnProperty(ANNOTATIONS) ? (cls as any)[ANNOTATIONS] : Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS]; annotations.push(annotationInstance); // 特定邏輯的執行 if (additionalProcessing) additionalProcessing(cls); return cls; }; } if (parentClass) { // 繼承父類 DecoratorFactory.prototype = Object.create(parentClass.prototype); } DecoratorFactory.prototype.ngMetadataName = name; (DecoratorFactory as any).annotationCls = DecoratorFactory; return DecoratorFactory as any; }); }
在上面的例子中,我們通過makeDecorator()
產生了一個用于定義組件的Component
裝飾器工廠。當使用@Component()
創建組件時,Angular 會根據元數據來編譯組件。
根據裝飾器元數據編譯組件
Angular 會根據該裝飾器元數據,來編譯 Angular 組件,然后將生成的組件定義(?cmp
)修補到組件類型上:
export function compileComponent(type: Type<any>, metadata: Component): void { // 初始化 ngDevMode (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode(); let ngComponentDef: any = null; // 元數據可能具有需要解析的資源 maybeQueueResolutionOfComponentResources(type, metadata); // 這里使用的功能與指令相同,因為這只是創建 ngFactoryDef 所需的元數據的子集 addDirectiveFactoryDef(type, metadata); Object.defineProperty(type, NG_COMP_DEF, { get: () => { if (ngComponentDef === null) { const compiler = getCompilerFacade(); // 根據元數據解析組件 if (componentNeedsResolution(metadata)) { ... // 異常處理 } ... // 創建編譯組件需要的完整元數據 const templateUrl = metadata.templateUrl || `ng:///${type.name}/template.html`; const meta: R3ComponentMetadataFacade = { ...directiveMetadata(type, metadata), typeSourceSpan: compiler.createParseSourceSpan('Component', type.name, templateUrl), template: metadata.template || '', preserveWhitespaces, styles: metadata.styles || EMPTY_ARRAY, animations: metadata.animations, directives: [], changeDetection: metadata.changeDetection, pipes: new Map(), encapsulation, interpolation: metadata.interpolation, viewProviders: metadata.viewProviders || null, }; // 編譯過程需要計算深度,以便確認編譯是否最終完成 compilationDepth++; try { if (meta.usesInheritance) { addDirectiveDefToUndecoratedParents(type); } // 根據模板、環境和組件需要的元數據,來編譯組件 ngComponentDef = compiler.compileComponent(angularCoreEnv, templateUrl, meta); } finally { // 即使編譯失敗,也請確保減少編譯深度 compilationDepth--; } if (compilationDepth === 0) { // 當執行 NgModule 裝飾器時,我們將模塊定義加入隊列,以便僅在所有聲明都已解析的情況下才將隊列出隊,并將其自身作為模塊作用域添加到其所有聲明中 // 此調用運行檢查以查看隊列中的任何模塊是否可以出隊,并將范圍添加到它們的聲明中 flushModuleScopingQueueAsMuchAsPossible(); } // 如果組件編譯是異步的,則聲明該組件的 @NgModule 批注可以執行并在組件類型上設置 ngSelectorScope 屬性 // 這允許組件在完成編譯后,使用模塊中的 directiveDefs 對其自身進行修補 if (hasSelectorScope(type)) { const scopes = transitiveScopesFor(type.ngSelectorScope); patchComponentDefWithScope(ngComponentDef, scopes); } } return ngComponentDef; }, ... }); }
編譯組件的過程可能是異步的(比如需要解析組件模板或其他資源的 URL)。如果編譯不是立即進行的,compileComponent
會將資源解析加入到全局隊列中,并且將無法返回?cmp
,直到通過調用resolveComponentResources
解決了全局隊列為止。
編譯過程中的元數據
元數據是有關類的信息,但它不是類的屬性。因此,用于配置類的定義和行為的這些數據,不應該存儲在該類的實例中,我們還需要在其他地方保存此數據。
在 Angular 中,編譯過程產生的元數據,會使用CompileMetadataResolver
來進行管理和維護,這里我們主要看指令(組件)相關的邏輯:
export class CompileMetadataResolver { private _nonNormalizedDirectiveCache = new Map<Type, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>(); // 使用 Map 的方式來保存 private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>(); private _summaryCache = new Map<Type, cpl.CompileTypeSummary|null>(); private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>(); private _ngModuleCache = new Map<Type, cpl.CompileNgModuleMetadata>(); private _ngModuleOfTypes = new Map<Type, Type>(); private _shallowModuleCache = new Map<Type, cpl.CompileShallowModuleMetadata>(); constructor( private _config: CompilerConfig, private _htmlParser: HtmlParser, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>, private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _console: Console, private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector, private _errorCollector?: ErrorCollector) {} // 清除特定某個指令的元數據 clearCacheFor(type: Type) { const dirMeta = this._directiveCache.get(type); this._directiveCache.delete(type); ... } // 清除所有元數據 clearCache(): void { this._directiveCache.clear(); ... } /** * 加載 NgModule 中,已聲明的指令和的管道 */ loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): Promise<any> { const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); const loading: Promise<any>[] = []; if (ngModule) { ngModule.declaredDirectives.forEach((id) => { const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync); if (promise) { loading.push(promise); } }); ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference)); } return Promise.all(loading); } // 加載指令(組件)元數據 loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> { // 若已加載,則直接返回 if (this._directiveCache.has(directiveType)) { return null; } directiveType = resolveForwardRef(directiveType); const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType)!; // 創建指令(組件)元數據 const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata|null) => { const normalizedDirMeta = new cpl.CompileDirectiveMetadata({ isHost: false, type: metadata.type, isComponent: metadata.isComponent, selector: metadata.selector, exportAs: metadata.exportAs, changeDetection: metadata.changeDetection, inputs: metadata.inputs, outputs: metadata.outputs, hostListeners: metadata.hostListeners, hostProperties: metadata.hostProperties, hostAttributes: metadata.hostAttributes, providers: metadata.providers, viewProviders: metadata.viewProviders, queries: metadata.queries, guards: metadata.guards, viewQueries: metadata.viewQueries, entryComponents: metadata.entryComponents, componentViewType: metadata.componentViewType, rendererType: metadata.rendererType, componentFactory: metadata.componentFactory, template: templateMetadata }); if (templateMetadata) { this.initComponentFactory(metadata.componentFactory!, templateMetadata.ngContentSelectors); } // 存儲完整的元數據信息,以及元數據摘要信息 this._directiveCache.set(directiveType, normalizedDirMeta); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); return null; }; if (metadata.isComponent) { // 如果是組件,該過程可能為異步過程,則需要等待異步過程結束后的模板返回 const template = metadata.template !; const templateMeta = this._directiveNormalizer.normalizeTemplate({ ngModuleType, componentType: directiveType, moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation), encapsulation: template.encapsulation, template: template.template, templateUrl: template.templateUrl, styles: template.styles, styleUrls: template.styleUrls, animations: template.animations, interpolation: template.interpolation, preserveWhitespaces: template.preserveWhitespaces }); if (isPromise(templateMeta) && isSync) { this._reportError(componentStillLoadingError(directiveType), directiveType); return null; } // 并將元數據進行存儲 return SyncAsync.then(templateMeta, createDirectiveMetadata); } else { // 指令,直接存儲元數據 createDirectiveMetadata(null); return null; } } // 獲取給定指令(組件)的元數據信息 getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata { const dirMeta = this._directiveCache.get(directiveType)!; ... return dirMeta; } // 獲取給定指令(組件)的元數據摘要信息 getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary { const dirSummary = <cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); ... return dirSummary; } }
可以看到,在編譯過程中,不管是組件、指令、管道,還是模塊,這些類在編譯過程中的元數據,都使用Map
來存儲。
到此,相信大家對“Angular中的元數據和裝飾器怎么應用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。