中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Angular中的元數據和裝飾器怎么應用

發布時間:2022-02-28 12:00:23 來源:億速云 閱讀:178 作者:iii 欄目:web開發

本篇內容主要講解“Angular中的元數據和裝飾器怎么應用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Angular中的元數據和裝飾器怎么應用”吧!

Angular中的元數據和裝飾器怎么應用

作為“為大型前端項目”而設計的前端框架,Angular 其實有許多值得參考和學習的設計,本系列主要用于研究這些設計和功能的實現原理。本文主要圍繞 Angular 中隨處可見的元數據,來進行介紹。

裝飾器是使用 Angular 進行開發時的核心概念。在 Angular 中,裝飾器用于為類或屬性附加元數據,來讓自己知道那些類或屬性的含義,以及該如何處理它們。

裝飾器與元數據

不管是裝飾器還是元數據,都不是由 Angular 提出的概念。因此,我們先來簡單了解一下。

元數據(Metadata)

在通用的概念中,元數據是描述用戶數據的數據。它總結了有關數據的基本信息,可以使查找和使用特定數據實例更加容易。例如,作者,創建日期,修改日期和文件大小是非常基本的文檔元數據的示例。

在用于類的場景下,元數據用于裝飾類,來描述類的定義和行為,以便可以配置類的預期行為。

裝飾器(Decorator)

裝飾器是 JavaScript 的一種語言特性,是一項位于階段 2(stage 2)的試驗特性。

裝飾器是定義期間在類,類元素或其他 JavaScript 語法形式上調用的函數。

裝飾器具有三個主要功能:

  • 可以用具有相同語義的匹配值替換正在修飾的值。(例如,裝飾器可以將方法替換為另一種方法,將一個字段替換為另一個字段,將一個類替換為另一個類,等等)。

  • 可以將元數據與正在修飾的值相關聯;可以從外部讀取此元數據,并將其用于元編程和自我檢查。

  • 可以通過元數據提供對正在修飾的值的訪問。對于公共值,他們可以通過值名稱來實現;對于私有值,它們接收訪問器函數,然后可以選擇共享它們。

本質上,裝飾器可用于對值進行元編程和向其添加功能,而無需從根本上改變其外部行為。

更多的內容,可以參考 tc39/proposal-decorators 提案。

Angular 中的裝飾器和元數據

我們在開發 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中的元數據和裝飾器怎么應用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

元阳县| 都兰县| 潼关县| 六安市| 綦江县| 新宁县| 榆树市| 上高县| 镶黄旗| 西吉县| 中方县| 东宁县| 建水县| 伊春市| 大余县| 双柏县| 改则县| 河东区| 和林格尔县| 巴彦淖尔市| 深水埗区| 海晏县| 涟源市| 徐闻县| 友谊县| 明水县| 长子县| 武清区| 浑源县| 旺苍县| 靖西县| 呼玛县| 黔西县| 杭州市| 大理市| 霍邱县| 林芝县| 土默特右旗| 昌黎县| 鲁山县| 麻江县|