亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    淺析Angular中的多級(jí)依賴注入設(shè)計(jì)

    本篇文章帶大家進(jìn)行Angular源碼學(xué)習(xí),介紹一下多級(jí)依賴注入設(shè)計(jì),希望對(duì)大家有所幫助!

    淺析Angular中的多級(jí)依賴注入設(shè)計(jì)

    作為“為大型前端項(xiàng)目”而設(shè)計(jì)的前端框架,Angular 其實(shí)有許多值得參考和學(xué)習(xí)的設(shè)計(jì),本系列主要用于研究這些設(shè)計(jì)和功能的實(shí)現(xiàn)原理。本文主要圍繞 Angular 中的最大特點(diǎn)——依賴注入,介紹 Angular 中多級(jí)依賴注入的設(shè)計(jì)?!鞠嚓P(guān)教程推薦:《angular教程》】

    上一篇我們介紹了 Angular 中的Injectot注入器、Provider提供者,以及注入器機(jī)制。那么,在 Angular 應(yīng)用中,各個(gè)組件和模塊間又是怎樣共享依賴的,同樣的服務(wù)是否可以多次實(shí)例化呢?

    組件和模塊的依賴注入過(guò)程,離不開(kāi) Angular 多級(jí)依賴注入的設(shè)計(jì),我們來(lái)看看。

    多級(jí)依賴注入

    前面我們說(shuō)過(guò),Angular 中的注入器是可繼承、且分層的。

    在 Angular 中,有兩個(gè)注入器層次結(jié)構(gòu):

    • ModuleInjector模塊注入器:使用@NgModule()@Injectable()注解在此層次結(jié)構(gòu)中配置ModuleInjector
    • ElementInjector元素注入器:在每個(gè) DOM 元素上隱式創(chuàng)建

    模塊注入器和元素注入器都是樹(shù)狀結(jié)構(gòu)的,但它們的分層結(jié)構(gòu)并不完全一致。

    模塊注入器

    模塊注入器的分層結(jié)構(gòu),除了與應(yīng)用中模塊設(shè)計(jì)有關(guān)系,還有平臺(tái)模塊(PlatformModule)注入器與應(yīng)用程序模塊(AppModule)注入器的分層結(jié)構(gòu)。

    平臺(tái)模塊(PlatformModule)注入器

    在 Angular 術(shù)語(yǔ)中,平臺(tái)是供 Angular 應(yīng)用程序在其中運(yùn)行的上下文。Angular 應(yīng)用程序最常見(jiàn)的平臺(tái)是 Web 瀏覽器,但它也可以是移動(dòng)設(shè)備的操作系統(tǒng)或 Web 服務(wù)器。

    Angular 應(yīng)用在啟動(dòng)時(shí),會(huì)創(chuàng)建一個(gè)平臺(tái)層:

    • 平臺(tái)是 Angular 在網(wǎng)頁(yè)上的入口點(diǎn),每個(gè)頁(yè)面只有一個(gè)平臺(tái)
    • 頁(yè)面上運(yùn)行的每個(gè) Angular 應(yīng)用程序,所共有的服務(wù)都在平臺(tái)內(nèi)綁定

    一個(gè) Angular 平臺(tái),主要包括創(chuàng)建模塊實(shí)例、銷(xiāo)毀等功能:

    @Injectable() export class PlatformRef {   // 傳入注入器,作為平臺(tái)注入器   constructor(private _injector: Injector) {}    // 為給定的平臺(tái)創(chuàng)建一個(gè) @NgModule 的實(shí)例,以進(jìn)行離線編譯   bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):       Promise<NgModuleRef<M>> {}    // 使用給定的運(yùn)行時(shí)編譯器,為給定的平臺(tái)創(chuàng)建一個(gè) @NgModule 的實(shí)例   bootstrapModule<M>(       moduleType: Type<M>,       compilerOptions: (CompilerOptions&BootstrapOptions)|       Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {}    // 注冊(cè)銷(xiāo)毀平臺(tái)時(shí)要調(diào)用的偵聽(tīng)器   onDestroy(callback: () => void): void {}    // 獲取平臺(tái)注入器   // 該平臺(tái)注入器是頁(yè)面上每個(gè) Angular 應(yīng)用程序的父注入器,并提供單例提供程序   get injector(): Injector {}    // 銷(xiāo)毀頁(yè)面上的當(dāng)前 Angular 平臺(tái)和所有 Angular 應(yīng)用程序,包括銷(xiāo)毀在平臺(tái)上注冊(cè)的所有模塊和偵聽(tīng)器   destroy() {} }

    實(shí)際上,平臺(tái)在啟動(dòng)的時(shí)候(bootstrapModuleFactory方法中),在ngZone.run中創(chuàng)建ngZoneInjector,以便在 Angular 區(qū)域中創(chuàng)建所有實(shí)例化的服務(wù),而ApplicationRef(頁(yè)面上運(yùn)行的 Angular 應(yīng)用程序)將在 Angular 區(qū)域之外創(chuàng)建。

    在瀏覽器中啟動(dòng)時(shí),會(huì)創(chuàng)建瀏覽器平臺(tái):

    export const platformBrowser: (extraProviders?: StaticProvider[]) => PlatformRef =     createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);  // 其中,platformCore 平臺(tái)必須包含在任何其他平臺(tái)中 export const platformCore = createPlatformFactory(null, 'core', _CORE_PLATFORM_PROVIDERS);

    使用平臺(tái)工廠(例如上面的createPlatformFactory)創(chuàng)建平臺(tái)時(shí),將隱式初始化頁(yè)面的平臺(tái):

    export function createPlatformFactory(     parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef)|null, name: string,     providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) => PlatformRef {   const desc = `Platform: ${name}`;   const marker = new InjectionToken(desc); // DI 令牌   return (extraProviders: StaticProvider[] = []) => {     let platform = getPlatform();     // 若平臺(tái)已創(chuàng)建,則不做處理     if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {       if (parentPlatformFactory) {         // 若有父級(jí)平臺(tái),則直接使用父級(jí)平臺(tái),并更新相應(yīng)的提供者         parentPlatformFactory(             providers.concat(extraProviders).concat({provide: marker, useValue: true}));       } else {         const injectedProviders: StaticProvider[] =             providers.concat(extraProviders).concat({provide: marker, useValue: true}, {               provide: INJECTOR_SCOPE,               useValue: 'platform'             });         // 若無(wú)父級(jí)平臺(tái),則新建注入器,并創(chuàng)建平臺(tái)         createPlatform(Injector.create({providers: injectedProviders, name: desc}));       }     }     return assertPlatform(marker);   }; }

    通過(guò)以上過(guò)程,我們知道 Angular 應(yīng)用在創(chuàng)建平臺(tái)的時(shí)候,創(chuàng)建平臺(tái)的模塊注入器ModuleInjector。我們從上一節(jié)Injector定義中也能看到,NullInjector是所有注入器的頂部:

    export abstract class Injector {   static NULL: Injector = new NullInjector(); }

    因此,在平臺(tái)模塊注入器之上,還有NullInjector()。而在平臺(tái)模塊注入器之下,則還有應(yīng)用程序模塊注入器。

    應(yīng)用程序根模塊(AppModule)注入器

    每個(gè)應(yīng)用程序有至少一個(gè) Angular 模塊,根模塊就是用來(lái)啟動(dòng)此應(yīng)用的模塊:

    @NgModule({ providers: APPLICATION_MODULE_PROVIDERS }) export class ApplicationModule {   // ApplicationRef 需要引導(dǎo)程序提供組件   constructor(appRef: ApplicationRef) {} }

    AppModule根應(yīng)用模塊由BrowserModule重新導(dǎo)出,當(dāng)我們使用 CLI 的new命令創(chuàng)建新應(yīng)用時(shí),它會(huì)自動(dòng)包含在根AppModule中。應(yīng)用程序根模塊中,提供者關(guān)聯(lián)著內(nèi)置的 DI 令牌,用于為引導(dǎo)程序配置根注入器。

    Angular 還將ComponentFactoryResolver添加到根模塊注入器中。此解析器存儲(chǔ)了entryComponents系列工廠,因此它負(fù)責(zé)動(dòng)態(tài)創(chuàng)建組件。

    模塊注入器層級(jí)

    到這里,我們可以簡(jiǎn)單地梳理出模塊注入器的層級(jí)關(guān)系:

    • 模塊注入器樹(shù)的最上層則是應(yīng)用程序根模塊(AppModule)注入器,稱(chēng)作 root。

    • 在 root 之上還有兩個(gè)注入器,一個(gè)是平臺(tái)模塊(PlatformModule)注入器,一個(gè)是NullInjector()。

    因此,模塊注入器的分層結(jié)構(gòu)如下:

    淺析Angular中的多級(jí)依賴注入設(shè)計(jì)

    在我們實(shí)際的應(yīng)用中,它很可能是這樣的:

    淺析Angular中的多級(jí)依賴注入設(shè)計(jì)

    Angular DI 具有分層注入體系,這意味著下級(jí)注入器也可以創(chuàng)建它們自己的服務(wù)實(shí)例。

    元素注入器

    前面說(shuō)過(guò),在 Angular 中有兩個(gè)注入器層次結(jié)構(gòu),分別是模塊注入器和元素注入器。

    元素注入器的引入

    當(dāng) Angular 中懶加載的模塊開(kāi)始廣泛使用時(shí),出現(xiàn)了一個(gè) issue:依賴注入系統(tǒng)導(dǎo)致懶加載模塊的實(shí)例化加倍。

    在這一次修復(fù)中,引入了新的設(shè)計(jì):注入器使用兩棵并行的樹(shù),一棵用于元素,另一棵用于模塊。

    Angular 會(huì)為所有entryComponents創(chuàng)建宿主工廠,它們是所有其他組件的根視圖。

    這意味著每次我們創(chuàng)建動(dòng)態(tài) Angular 組件時(shí),都會(huì)使用根數(shù)據(jù)(RootData)創(chuàng)建根視圖(RootView):

    class ComponentFactory_ extends ComponentFactory<any>{   create(       injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,       ngModule?: NgModuleRef<any>): ComponentRef<any> {     if (!ngModule) {       throw new Error('ngModule should be provided');     }     const viewDef = resolveDefinition(this.viewDefFactory);     const componentNodeIndex = viewDef.nodes[0].element!.componentProvider!.nodeIndex;     // 使用根數(shù)據(jù)創(chuàng)建根視圖     const view = Services.createRootView(         injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);     // view.nodes 的訪問(wèn)器     const component = asProviderData(view, componentNodeIndex).instance;     if (rootSelectorOrNode) {       view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);     }     // 創(chuàng)建組件     return new ComponentRef_(view, new ViewRef_(view), component);   } }

    該根數(shù)據(jù)(RootData)包含對(duì)elInjectorngModule注入器的引用:

    function createRootData(     elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2,     projectableNodes: any[][], rootSelectorOrNode: any): RootData {   const sanitizer = ngModule.injector.get(Sanitizer);   const errorHandler = ngModule.injector.get(ErrorHandler);   const renderer = rendererFactory.createRenderer(null, null);   return {     ngModule,     injector: elInjector,     projectableNodes,     selectorOrNode: rootSelectorOrNode,     sanitizer,     rendererFactory,     renderer,     errorHandler,   }; }

    引入元素注入器樹(shù),原因是這樣的設(shè)計(jì)比較簡(jiǎn)單。通過(guò)更改注入器層次結(jié)構(gòu),避免交錯(cuò)插入模塊和組件注入器,從而導(dǎo)致延遲加載模塊的雙倍實(shí)例化。因?yàn)槊總€(gè)注入器都只有一個(gè)父對(duì)象,并且每次解析都必須精確地尋找一個(gè)注入器來(lái)檢索依賴項(xiàng)。

    元素注入器(Element Injector)

    在 Angular 中,視圖是模板的表示形式,它包含不同類(lèi)型的節(jié)點(diǎn),其中便有元素節(jié)點(diǎn),元素注入器位于此節(jié)點(diǎn)上:

    export interface ElementDef {   ...   // 在該視圖中可見(jiàn)的 DI 的公共提供者   publicProviders: {[tokenKey: string]: NodeDef}|null;   // 與 visiblePublicProviders 相同,但還包括位于此元素上的私有提供者   allProviders: {[tokenKey: string]: NodeDef}|null; }

    默認(rèn)情況下ElementInjector為空,除非在@Directive()@Component()providers屬性中進(jìn)行配置。

    當(dāng) Angular 為嵌套的 HTML 元素創(chuàng)建元素注入器時(shí),要么從父元素注入器繼承它,要么直接將父元素注入器分配給子節(jié)點(diǎn)定義。

    如果子 HTML 元素上的元素注入器具有提供者,則應(yīng)該繼承該注入器。否則,無(wú)需為子組件創(chuàng)建單獨(dú)的注入器,并且如果需要,可以直接從父級(jí)的注入器中解決依賴項(xiàng)。

    元素注入器與模塊注入器的設(shè)計(jì)

    那么,元素注入器與模塊注入器是從哪個(gè)地方開(kāi)始成為平行樹(shù)的呢?

    我們已經(jīng)知道,應(yīng)用程序根模塊(AppModule)會(huì)在使用 CLI 的new命令創(chuàng)建新應(yīng)用時(shí),自動(dòng)包含在根AppModule中。

    當(dāng)應(yīng)用程序(ApplicationRef)啟動(dòng)(bootstrap)時(shí),會(huì)創(chuàng)建entryComponent

    const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule);

    該過(guò)程會(huì)使用根數(shù)據(jù)(RootData)創(chuàng)建根視圖(RootView),同時(shí)會(huì)創(chuàng)建根元素注入器,在這里elInjectorInjector.NULL。

    在這里,Angular 的注入器樹(shù)被分成元素注入器樹(shù)和模塊注入器樹(shù),這兩個(gè)平行的樹(shù)了。

    Angular 會(huì)有規(guī)律的創(chuàng)建下級(jí)注入器,每當(dāng) Angular 創(chuàng)建一個(gè)在@Component()中指定了providers的組件實(shí)例時(shí),它也會(huì)為該實(shí)例創(chuàng)建一個(gè)新的子注入器。類(lèi)似的,當(dāng)在運(yùn)行期間加載一個(gè)新的NgModule時(shí),Angular 也可以為它創(chuàng)建一個(gè)擁有自己的提供者的注入器。

    子模塊和組件注入器彼此獨(dú)立,并且會(huì)為所提供的服務(wù)分別創(chuàng)建自己的實(shí)例。當(dāng) Angular 銷(xiāo)毀NgModule或組件實(shí)例時(shí),也會(huì)銷(xiāo)毀這些注入器以及注入器中的那些服務(wù)實(shí)例。

    Angular 解析依賴過(guò)程

    上面我們介紹了 Angular 中的兩種注入器樹(shù):模塊注入器樹(shù)和元素注入器樹(shù)。那么,Angular 在提供依賴時(shí),又會(huì)以怎樣的方式去進(jìn)行解析呢。

    在 Angular 種,當(dāng)為組件/指令解析 token 獲取依賴時(shí),Angular 分為兩個(gè)階段來(lái)解析它:

    • 針對(duì)ElementInjector層次結(jié)構(gòu)(其父級(jí))
    • 針對(duì)ModuleInjector層次結(jié)構(gòu)(其父級(jí))

    其過(guò)程如下(參考多級(jí)注入器-解析規(guī)則):

    • 當(dāng)組件聲明依賴項(xiàng)時(shí),Angular 會(huì)嘗試使用它自己的ElementInjector來(lái)滿足該依賴。

    • 如果組件的注入器缺少提供者,它將把請(qǐng)求傳給其父組件的ElementInjector。

    • 這些請(qǐng)求將繼續(xù)轉(zhuǎn)發(fā),直到 Angular 找到可以處理該請(qǐng)求的注入器或用完祖先ElementInjector。

    • 如果 Angular 在任何ElementInjector中都找不到提供者,它將返回到發(fā)起請(qǐng)求的元素,并在ModuleInjector層次結(jié)構(gòu)中進(jìn)行查找。

    • 如果 Angular 仍然找不到提供者,它將引發(fā)錯(cuò)誤。

    為此,Angular 引入一種特殊的合并注入器。

    合并注入器(Merge Injector)

    合并注入器本身沒(méi)有任何值,它只是視圖和元素定義的組合。

    class Injector_ implements Injector {   constructor(private view: ViewData, private elDef: NodeDef|null) {}   get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {     const allowPrivateServices =         this.elDef ? (this.elDef.flags & NodeFlags.ComponentView) !== 0 : false;     return Services.resolveDep(         this.view, this.elDef, allowPrivateServices,         {flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);   } }

    當(dāng) Angular 解析依賴項(xiàng)時(shí),合并注入器則是元素注入器樹(shù)和模塊注入器樹(shù)之間的橋梁。當(dāng) Angular 嘗試解析組件或指令中的某些依賴關(guān)系時(shí),會(huì)使用合并注入器來(lái)遍歷元素注入器樹(shù),然后,如果找不到依賴關(guān)系,則切換到模塊注入器樹(shù)以解決依賴關(guān)系。

    class ViewContainerRef_ implements ViewContainerData {   ...   // 父級(jí)試圖元素注入器的查詢   get parentInjector(): Injector {     let view = this._view;     let elDef = this._elDef.parent;     while (!elDef && view) {       elDef = viewParentEl(view);       view = view.parent!;     }      return view ? new Injector_(view, elDef) : new Injector_(this._view, null);   } }

    解析過(guò)程

    注入器是可繼承的,這意味著如果指定的注入器無(wú)法解析某個(gè)依賴,它就會(huì)請(qǐng)求父注入器來(lái)解析它。具體的解析算法在resolveDep()方法中實(shí)現(xiàn):

    export function resolveDep(     view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,     notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {   //   //          mod1   //         /   //       el1   mod2   //           /   //         el2   //   // 請(qǐng)求 el2.injector.get(token)時(shí),按以下順序檢查并返回找到的第一個(gè)值:   // - el2.injector.get(token, default)   // - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module   // - mod2.injector.get(token, default) }

    如果是<child></child>這樣模板的根AppComponent組件,那么在 Angular 中將具有三個(gè)視圖:

    <!-- HostView_AppComponent -->     <my-app></my-app> <!-- View_AppComponent -->     <child></child> <!-- View_ChildComponent -->     some content

    依賴解析過(guò)程,解析算法會(huì)基于視圖層次結(jié)構(gòu),如圖所示進(jìn)行:

    淺析Angular中的多級(jí)依賴注入設(shè)計(jì)

    如果在子組件中解析某些令牌,Angular 將:

    • 首先查看子元素注入器,進(jìn)行檢查elRef.element.allProviders|publicProviders。

    • 然后遍歷所有父視圖元素(1),并檢查元素注入器中的提供者。

    • 如果下一個(gè)父視圖元素等于null(2),則返回到startView(3),檢查startView.rootData.elnjector(4)。

    • 只有在找不到令牌的情況下,才檢查startView.rootData module.injector( 5 )。

    由此可見(jiàn),Angular 在遍歷組件以解析某些依賴性時(shí),將搜索特定視圖的父元素而不是特定元素的父元素。視圖的父元素可以通過(guò)以下方法獲得:

    // 對(duì)于組件視圖,這是宿主元素 // 對(duì)于嵌入式視圖,這是包含視圖容器的父節(jié)點(diǎn)的索引 export function viewParentEl(view: ViewData): NodeDef|null {   const parentView = view.parent;   if (parentView) {     return view.parentNodeDef !.parent;   } else {     return null;   } }

    總結(jié)

    本文主要介紹了 Angular 中注入器的層級(jí)結(jié)構(gòu),在 Angular 中有兩棵平行的注入器樹(shù):模塊注入器樹(shù)和元素注入器樹(shù)。

    元素注入器樹(shù)的引入,主要是為了解決依賴注入解析懶加載模塊時(shí),導(dǎo)致模塊的雙倍實(shí)例化問(wèn)題。在元素注入器樹(shù)引入后,Angular 解析依賴的過(guò)程也有調(diào)整,優(yōu)先尋找元素注入器以及父視圖元素注入器等注入器的依賴,只有元素注入器中無(wú)法找到令牌時(shí),才會(huì)查詢模塊注入器中的依賴。

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)