この記事では、Angular の学習を継続し、Angular のメタデータとデコレータを理解し、それらの使用法を簡単に理解します。
「大規模なフロントエンド プロジェクト向け」に設計されたフロントエンド フレームワークとして、Angular には実際に参考にして学ぶ価値のある設計が数多くあります。このシリーズは主に次の用途に使用されます。これらのデザインと機能を研究し、実現原理を研究します。この記事では、Angular の随所に存在するメタデータを主に紹介します。 [関連チュートリアルの推奨事項: "angular チュートリアル"]
デコレーターは、Angular を使用して開発する場合の中心的な概念です。 Angular では、デコレータを使用してクラスまたはプロパティにメタデータを添付し、それらのクラスまたはプロパティの意味とその処理方法を知らせます。
デコレータもメタデータも、Angular によって提案された概念ではありません。それでは、まず簡単に見てみましょう。
メタデータ
一般的な概念では、メタデータはユーザー データを説明するデータです。データに関する基本情報が要約されており、特定のデータ インスタンスの検索と使用が容易になります。たとえば、作成者、作成日、変更日、ファイル サイズなどは、非常に基本的なドキュメント メタデータの例です。
クラスに使用されるシナリオでは、メタデータを使用してクラスを装飾し、クラスの定義と動作を記述し、クラスの期待される動作を構成できるようにします。
Decorator
Decorator は JavaScript の言語機能で、ステージ 2. テスト特性にあります。
デコレーターは、定義中にクラス、クラス要素、またはその他の JavaScript 構文フォームで呼び出される関数です。
デコレータには 3 つの主な機能があります。
デコレータの値を、同じセマンティクスを持つ一致する値に置き換えることができます。 (たとえば、デコレータはメソッドを別のメソッドに置き換えたり、あるフィールドを別のフィールドに置き換えたり、あるクラスを別のクラスに置き換えたりすることができます)。
メタデータは、変更される値に関連付けることができます。このメタデータは外部から読み取って、メタプログラミングや自己検査に使用できます。
変更される値へのアクセスは、メタデータを通じて提供できます。パブリック値の場合は、値名を介してこれを行い、プライベート値の場合は、オプションで共有できるアクセサー関数を受け取ります。
本質的に、デコレータを使用すると、外部の動作を根本的に変更することなく、値をメタプログラムし、それに機能を追加できます。
詳細については、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 中的装饰器和元数据,其中元数据用于描述类的定义和行为。
在 Angular 编译过程中,会使用Map
的数据结构来维护和存储装饰器的元数据,并根据这些元数据信息来编译组件、指令、管道和模块等。
更多编程相关知识,请访问:编程教学!!
以上がAngular のメタデータとデコレータについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。