Cet article poursuit l'apprentissage d'Angular, vous amène à comprendre les métadonnées et les décorateurs dans Angular, et comprend brièvement leur utilisation. J'espère qu'il sera utile à tout le monde !
En tant que framework front-end conçu "pour les projets front-end à grande échelle", Angular propose en fait de nombreuses conceptions qui méritent d'être référencées et apprises. Cette série est principalement utilisée pour étudier les principes de mise en œuvre de ces conceptions et fonctions. Cet article présente principalement les métadonnées que l'on trouve partout dans Angular. [Tutoriels associés recommandés : "Tutoriel angulaire"]
Les décorateurs sont le concept de base lors du développement avec Angular. Dans Angular, les décorateurs sont utilisés pour attacher des métadonnées à des classes ou des propriétés afin de vous faire savoir ce que signifient ces classes ou propriétés et comment les gérer.
Ni les décorateurs ni les métadonnées ne sont des concepts proposés par Angular. Alors jetons-y un coup d’œil rapide d’abord.
Métadonnées
Dans un concept courant, les métadonnées sont des données qui décrivent les données utilisateur. Il résume les informations de base sur les données et peut faciliter la recherche et l'utilisation d'instances de données spécifiques. Par exemple, l'auteur, la date de création, la date de modification et la taille du fichier sont des exemples de métadonnées de document très basiques.
Dans le cas des classes, les métadonnées sont utilisées pour décorer la classe afin de décrire la définition et le comportement de la classe afin que le comportement attendu de la classe puisse être configuré.
Decorator
Decorator est une fonctionnalité du langage JavaScript et une fonctionnalité expérimentale à l'étape 2.
Les décorateurs sont des fonctions appelées lors de la définition sur une classe, un élément de classe ou une autre forme de syntaxe JavaScript.
Les décorateurs ont trois fonctions principales :
peuvent remplacer la valeur à décorer par une valeur correspondante ayant la même sémantique. (Par exemple, un décorateur peut remplacer une méthode par une autre méthode, un champ par un autre champ, une classe par une autre classe, etc.).
Il est possible d'associer des métadonnées à la valeur en cours de modification ; ces métadonnées peuvent être lues en externe et utilisées pour la métaprogrammation et l'auto-inspection.
L'accès à la valeur en cours de modification peut être fourni via des métadonnées. Pour les valeurs publiques, ils le font via le nom de la valeur ; pour les valeurs privées, ils reçoivent des fonctions d'accès, qui peuvent ensuite éventuellement être partagées.
Essentiellement, les décorateurs peuvent être utilisés pour métaprogrammer une valeur et lui ajouter des fonctionnalités sans changer fondamentalement son comportement externe.
Pour plus de contenu, veuillez vous référer à la proposition tc39/proposal-decorators.
Lorsque nous développons des applications Angular, qu'il s'agisse de composants, d'instructions, de services, de modules, etc., nous devons utiliser des décorateurs pour les définir et les développer. Les décorateurs apparaissent immédiatement avant la définition d'une classe et sont utilisés pour déclarer que la classe a un type spécifié et fournir des métadonnées adaptées à ce type.
Par exemple, nous pouvons utiliser les décorateurs suivants pour déclarer les classes angulaires : @Component()
, @Directive()
, @Pipe()
, @Injectable()
, @NgModule()
. @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
@Component()
comme exemple, les fonctions de ce décorateur incluent : 🎜🎜🎜🎜Marquer la classe comme Composants angulaires. 🎜🎜🎜🎜Fournit des métadonnées configurables qui déterminent comment le composant doit être traité, instancié et utilisé au moment de l'exécution. 🎜🎜🎜🎜Pour savoir comment utiliser @Component()
, veuillez vous référer à 🎜, pas beaucoup d'introduction ici. Jetons un coup d'œil à la définition de ce décorateur : 🎜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; }, ... }); }
makeDecorator()
: 🎜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; } }
makeDecorator()
pour générer une fabrique de décorateurs Component
pour définir les composants. Lorsque vous créez un composant à l'aide de @Component()
, Angular compile le composant en fonction des métadonnées. 🎜🎜🎜🎜Compile les composants basés sur les métadonnées du décorateur🎜🎜🎜🎜Angular compilera les composants angulaires basés sur les métadonnées du décorateur, puis patchera la définition du composant générée (ɵcmp
) au type de composant :🎜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
的数据结构来维护和存储装饰器的元数据,并根据这些元数据信息来编译组件、指令、管道和模块等。
更多编程相关知识,请访问:编程教学!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!