<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當前位置: 首頁 - 科技 - 知識百科 - 正文

        Angular Renderer (渲染器)的具體使用

        來源:懂視網 責編:小采 時間:2020-11-27 22:15:19
        文檔

        Angular Renderer (渲染器)的具體使用

        Angular Renderer (渲染器)的具體使用:Angular 其中的一個設計目標是使瀏覽器與 DOM 獨立。DOM 是復雜的,因此使組件與它分離,會讓我們的應用程序,更容易測試與重構。另外的好處是,由于這種解耦,使得我們的應用能夠運行在其它平臺 (比如:Node.js、WebWorkers、NativeScript 等
        推薦度:
        導讀Angular Renderer (渲染器)的具體使用:Angular 其中的一個設計目標是使瀏覽器與 DOM 獨立。DOM 是復雜的,因此使組件與它分離,會讓我們的應用程序,更容易測試與重構。另外的好處是,由于這種解耦,使得我們的應用能夠運行在其它平臺 (比如:Node.js、WebWorkers、NativeScript 等

        Angular 其中的一個設計目標是使瀏覽器與 DOM 獨立。DOM 是復雜的,因此使組件與它分離,會讓我們的應用程序,更容易測試與重構。另外的好處是,由于這種解耦,使得我們的應用能夠運行在其它平臺 (比如:Node.js、WebWorkers、NativeScript 等)。

        為了能夠支持跨平臺,Angular 通過抽象層封裝了不同平臺的差異。比如定義了抽象類 Renderer、Renderer2 、抽象類 RootRenderer 等。此外還定義了以下引用類型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。

        本文的主要內容是分析 Angular 中 Renderer (渲染器),不過在進行具體分析前,我們先來介紹一下平臺的概念。

        平臺

        什么是平臺

        平臺是應用程序運行的環境。它是一組服務,可以用來訪問你的應用程序和 Angular 框架本身的內置功能。由于Angular 主要是一個 UI 框架,平臺提供的最重要的功能之一就是頁面渲染。

        平臺和引導應用程序

        在我們開始構建一個自定義渲染器之前,我們來看一下如何設置平臺,以及引導應用程序。

        import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
        import {BrowserModule} from '@angular/platform-browser';
        
        @NgModule({
         imports: [BrowserModule],
         bootstrap: [AppCmp]
        })
        class AppModule {}
        
        platformBrowserDynamic().bootstrapModule(AppModule);

        如你所見,引導過程由兩部分組成:創建平臺和引導模塊。在這個例子中,我們導入 BrowserModule 模塊,它是瀏覽器平臺的一部分。應用中只能有一個激活的平臺,但是我們可以利用它來引導多個模塊,如下所示:

        const platformRef: PlatformRef = platformBrowserDynamic();
        platformRef.bootstrapModule(AppModule1);
        platformRef.bootstrapModule(AppModule2);

        由于應用中只能有一個激活的平臺,單例的服務必須在該平臺中注冊。比如,瀏覽器只有一個地址欄,對應的服務對象就是單例。此外如何讓我們自定義的 UI 界面,能夠在瀏覽器中顯示出來呢,這就需要使用 Angular 為我們提供的渲染器。

        渲染器

        什么是渲染器

        渲染器是 Angular 為我們提供的一種內置服務,用于執行 UI 渲染操作。在瀏覽器中,渲染是將模型映射到視圖的過程。模型的值可以是 JavaScript 中的原始數據類型、對象、數組或其它的數據對象。然而視圖可以是頁面中的段落、表單、按鈕等其他元素,這些頁面元素內部使用 DOM (Document Object Model) 來表示。

        Angular Renderer

        RootRenderer

        export abstract class RootRenderer {
         abstract renderComponent(componentType: RenderComponentType): Renderer;
        }

        Renderer

        /**
         * @deprecated Use the `Renderer2` instead.
         */
        export abstract class Renderer {
         abstract createElement(parentElement: any, name: string, 
         debugInfo?: RenderDebugInfo): any;
         abstract createText(parentElement: any, value: string, 
         debugInfo?: RenderDebugInfo): any;
         abstract listen(renderElement: any, name: string, callback: Function): Function;
         abstract listenGlobal(target: string, name: string, callback: Function): Function;
         abstract setElementProperty(renderElement: any, propertyName: string, propertyValue: 
         any): void;
         abstract setElementAttribute(renderElement: any, attributeName: string, 
         attributeValue: string): void;
         // ...
        }

        Renderer2

        export abstract class Renderer2 {
         abstract createElement(name: string, namespace?: string|null): any;
         abstract createComment(value: string): any;
         abstract createText(value: string): any;
         abstract setAttribute(el: any, name: string, value: string,
         namespace?: string|null): void;
         abstract removeAttribute(el: any, name: string, namespace?: string|null): void;
         abstract addClass(el: any, name: string): void;
         abstract removeClass(el: any, name: string): void;
         abstract setStyle(el: any, style: string, value: any, 
         flags?: RendererStyleFlags2): void;
         abstract removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void;
         abstract setProperty(el: any, name: string, value: any): void;
         abstract setValue(node: any, value: string): void;
         abstract listen(
         target: 'window'|'document'|'body'|any, eventName: string,
         callback: (event: any) => boolean | void): () => void;
        }

        需要注意的是在 Angular 4.x+ 版本,我們使用 Renderer2 替代 Renderer。通過觀察 Renderer 相關的抽象類 (Renderer、Renderer2),我們發現抽象類中定義了很多抽象方法,用來創建元素、文本、設置屬性、添加樣式和設置事件監聽等。

        渲染器如何工作

        在實例化一個組件時,Angular 會調用 renderComponent() 方法并將其獲取的渲染器與該組件實例相關聯。Angular 將會在渲染組件時通過渲染器執行對應相關的操作,比如,創建元素、設置屬性、添加樣式和訂閱事件等。

        使用 Renderer

        @Component({
         selector: 'exe-cmp',
         template: `
         <h3>Exe Component</h3>
         `
        })
        export class ExeComponent {
         constructor(private renderer: Renderer2, elRef: ElementRef) {
         this.renderer.setProperty(elRef.nativeElement, 'author', 'semlinker');
         }
        }

        以上代碼中,我們利用構造注入的方式,注入 Renderer2 和 ElementRef 實例。有些讀者可能會問,注入的實例對象是怎么生成的。這里我們只是稍微介紹一下相關知識,并不會詳細展開。具體代碼如下:

        TokenKey

        // packages/core/src/view/util.ts
        const _tokenKeyCache = new Map<any, string>();
        export function tokenKey(token: any): string {
         let key = _tokenKeyCache.get(token);
         if (!key) {
         key = stringify(token) + '_' + _tokenKeyCache.size;
         _tokenKeyCache.set(token, key);
         }
         return key;
        }
        
        // packages/core/src/view/provider.ts
        const RendererV1TokenKey = tokenKey(RendererV1);
        const Renderer2TokenKey = tokenKey(Renderer2);
        const ElementRefTokenKey = tokenKey(ElementRef);
        const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
        const TemplateRefTokenKey = tokenKey(TemplateRef);
        const ChangeDetectorRefTokenKey = tokenKey(ChangeDetectorRef);
        const InjectorRefTokenKey = tokenKey(Injector);

        resolveDep()

        export function resolveDep(
         view: ViewData, elDef: NodeDef, 
         allowPrivateServices: boolean, depDef: DepDef,
         notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
         const tokenKey = depDef.tokenKey;
         // ...
         while (view) {
         if (elDef) {
         switch (tokenKey) {
         case RendererV1TokenKey: { // tokenKey(RendererV1)
         const compView = findCompView(view, elDef, allowPrivateServices);
         return createRendererV1(compView);
         }
         case Renderer2TokenKey: { // tokenKey(Renderer2)
         const compView = findCompView(view, elDef, allowPrivateServices);
         return compView.renderer;
         }
         case ElementRefTokenKey: // tokenKey(ElementRef)
         return new ElementRef(asElementData(view, elDef.index).renderElement);
         // ... 此外還包括:ViewContainerRefTokenKey、TemplateRefTokenKey、
         // ChangeDetectorRefTokenKey 等
         }
         }
         }
         // ...
        }

        通過以上代碼,我們發現當我們在組件類的構造函數中聲明相應的依賴對象時,如 Renderer2 和 ElementRef,Angular 內部會調用 resolveDep() 方法,實例化 Token 對應依賴對象。

        在大多數情況下,我們開發的 Angular 應用程序是運行在瀏覽器平臺,接下來我們來了解一下該平臺下的默認渲染器 - DefaultDomRenderer2。

        DefaultDomRenderer2

        在瀏覽器平臺下,我們可以通過調用 DomRendererFactory2 工廠,根據不同的視圖封裝方案,創建對應渲染器。

        DomRendererFactory2

        // packages/platform-browser/src/dom/dom_renderer.ts
        @Injectable()
        export class DomRendererFactory2 implements RendererFactory2 {
         private rendererByCompId = new Map<string, Renderer2>();
         private defaultRenderer: Renderer2;
        
         constructor(
         private eventManager: EventManager, 
         private sharedStylesHost: DomSharedStylesHost) {
         // 創建默認的DOM渲染器
         this.defaultRenderer = new DefaultDomRenderer2(eventManager);
         };
        
         createRenderer(element: any, type: RendererType2|null): Renderer2 {
         if (!element || !type) {
         return this.defaultRenderer;
         }
         // 根據不同的視圖封裝方案,創建不同的渲染器
         switch (type.encapsulation) {
         // 無 Shadow DOM,但是通過 Angular 提供的樣式包裝機制來封裝組件,
         // 使得組件的樣式不受外部影響,這是 Angular 的默認設置。
         case ViewEncapsulation.Emulated: {
         let renderer = this.rendererByCompId.get(type.id);
         if (!renderer) {
         renderer =
         new EmulatedEncapsulationDomRenderer2(this.eventManager, 
         this.sharedStylesHost, type);
         this.rendererByCompId.set(type.id, renderer);
         }
         (<EmulatedEncapsulationDomRenderer2>renderer).applyToHost(element);
         return renderer;
         }
         // 使用原生的 Shadow DOM 特性 
         case ViewEncapsulation.Native:
         return new ShadowDomRenderer(this.eventManager, 
         this.sharedStylesHost, element, type);
         // 無 Shadow DOM,并且也無樣式包裝
         default: {
         // ...
         return this.defaultRenderer;
         }
         }
         }
        }

        上面代碼中的 EmulatedEncapsulationDomRenderer2ShadowDomRenderer 類都繼承于 DefaultDomRenderer2 類,接下來我們再來看一下 DefaultDomRenderer2 類的內部實現:

        class DefaultDomRenderer2 implements Renderer2 { 
         constructor(private eventManager: EventManager) {}
        
         // 省略 Renderer2 抽象類中定義的其它方法
         createElement(name: string, namespace?: string): any {
         if (namespace) {
         return document.createElementNS(NAMESPACE_URIS[namespace], name);
         }
         return document.createElement(name);
         }
        
         createComment(value: string): any { return document.createComment(value); }
        
         createText(value: string): any { return document.createTextNode(value); }
        
         addClass(el: any, name: string): void { el.classList.add(name); }
        
         setStyle(el: any, style: string, value: any, flags: RendererStyleFlags2): void {
         if (flags & RendererStyleFlags2.DashCase) {
         el.style.setProperty(
         style, value, !!(flags & RendererStyleFlags2.Important) ? 'important' : '');
         } else {
         el.style[style] = value;
         }
         }
        
         listen(
         target: 'window'|'document'|'body'|any, 
         event: string, 
         callback: (event: any) => boolean):
         () => void {
         checkNoSyntheticProp(event, 'listener');
         if (typeof target === 'string') {
         return <() => void>this.eventManager.addGlobalEventListener(
         target, event, decoratePreventDefault(callback));
         }
         return <() => void>this.eventManager.addEventListener(
         target, event, decoratePreventDefault(callback)) as() => void;
         }
        }

        介紹完 DomRendererFactory2DefaultDomRenderer2 類,最后我們來看一下 Angular 內部如何利用它們。

        DomRendererFactory2 內部應用

        BrowserModule

        // packages/platform-browser/src/browser.ts
        @NgModule({
         providers: [
         // 配置 DomRendererFactory2 和 RendererFactory2 provider
         DomRendererFactory2,
         {provide: RendererFactory2, useExisting: DomRendererFactory2},
         // ...
         ],
         exports: [CommonModule, ApplicationModule]
        })
        export class BrowserModule {
         constructor(@Optional() @SkipSelf() parentModule: BrowserModule) {
         // 用于判斷應用中是否已經導入BrowserModule模塊
         if (parentModule) {
         throw new Error(
         `BrowserModule has already been loaded. If you need access to common 
         directives such as NgIf and NgFor from a lazy loaded module, 
         import CommonModule instead.`);
         }
         }
        }

        createComponentView()

        // packages/core/src/view/view.ts
        export function createComponentView(
         parentView: ViewData, 
         nodeDef: NodeDef, 
         viewDef: ViewDefinition, 
         hostElement: any): ViewData {
         const rendererType = nodeDef.element !.componentRendererType; // 步驟一
         let compRenderer: Renderer2;
         if (!rendererType) { // 步驟二
         compRenderer = parentView.root.renderer;
         } else {
         compRenderer = parentView.root.rendererFactory
         .createRenderer(hostElement, rendererType);
         }
         
         return createView(
         parentView.root, compRenderer, parentView, 
         nodeDef.element !.componentProvider, viewDef);
        }

        步驟一

        當 Angular 在創建組件視圖時,會根據 nodeDef.element 對象的 componentRendererType 屬性值,來創建組件的渲染器。接下來我們先來看一下 NodeDefElementDefRendererType2 接口定義:

        // packages/core/src/view/types.ts
        // 視圖中節點的定義
        export interface NodeDef {
         bindingIndex: number;
         bindings: BindingDef[];
         bindingFlags: BindingFlags;
         outputs: OutputDef[];
         element: ElementDef|null; // nodeDef.element
         provider: ProviderDef|null;
         // ...
        }
        
        // 元素的定義
        export interface ElementDef {
         name: string|null;
         attrs: [string, string, string][]|null;
         template: ViewDefinition|null;
         componentProvider: NodeDef|null;
         // 設置組件渲染器的類型
         componentRendererType: RendererType2|null; // nodeDef.element.componentRendererType
         componentView: ViewDefinitionFactory|null;
         handleEvent: ElementHandleEventFn|null;
         // ...
        }
        
        // packages/core/src/render/api.ts
        // RendererType2 接口定義
        export interface RendererType2 {
         id: string;
         encapsulation: ViewEncapsulation; // Emulated、Native、None
         styles: (string|any[])[];
         data: {[kind: string]: any};
        }

        步驟二

        獲取 componentRendererType 的屬性值后,如果該值為 null 的話,則直接使用 parentView.root 屬性值對應的 renderer 對象。若該值不為空,則調用 parentView.root 對象的 rendererFactory() 方法創建 renderer 對象。

        通過上面分析,我們發現不管走哪條分支,我們都需要使用 parentView.root 對象,然而該對象是什么特殊對象?我們發現 parentView 的數據類型是 ViewData ,該數據接口定義如下:

        // packages/core/src/view/types.ts
        export interface ViewData {
         def: ViewDefinition;
         root: RootData;
         renderer: Renderer2;
         nodes: {[key: number]: NodeData};
         state: ViewState;
         oldValues: any[];
         disposables: DisposableFn[]|null;
         // ...
        }

        通過 ViewData 的接口定義,我們終于發現了 parentView.root 的屬性類型,即 RootData

        // packages/core/src/view/types.ts
        export interface RootData {
         injector: Injector;
         ngModule: NgModuleRef<any>;
         projectableNodes: any[][];
         selectorOrNode: any;
         renderer: Renderer2;
         rendererFactory: RendererFactory2;
         errorHandler: ErrorHandler;
         sanitizer: Sanitizer;
        }

        那好,現在問題來了:

        1. 什么時候創建 RootData 對象?
        2. 怎么創建 RootData 對象?

        什么時候創建 RootData 對象?

        當創建根視圖的時候會創建 RootData,在開發環境會調用 debugCreateRootView() 方法創建 RootView,而在生產環境會調用 createProdRootView() 方法創建 RootView。簡單起見,我們只分析 createProdRootView() 方法:

        function createProdRootView(
         elInjector: Injector, 
         projectableNodes: any[][], 
         rootSelectorOrNode: string | any,
         def: ViewDefinition, 
         ngModule: NgModuleRef<any>, 
         context?: any): ViewData {
         /** RendererFactory2 Provider 配置
         * DomRendererFactory2,
         * {provide: RendererFactory2, useExisting: DomRendererFactory2},
         */
         const rendererFactory: RendererFactory2 = ngModule.injector.get(RendererFactory2);
         
         return createRootView(
         createRootData(elInjector, ngModule, rendererFactory,
         projectableNodes, rootSelectorOrNode),
         def, context);
        }
        
        // 創建根視圖
        export function createRootView(root: RootData, def: ViewDefinition, 
         context?: any): ViewData {
         // 創建ViewData對象
         const view = createView(root, root.renderer, null, null, def);
         initView(view, context, context);
         createViewNodes(view);
         return view;
        }

        上面代碼中,當創建 RootView 的時候,會調用 createRootData() 方法創建 RootData 對象。最后一步就是分析 createRootData() 方法。

        怎么創建 RootData 對象?

        通過上面分析,我們知道通過 createRootData() 方法,來創建 RootData 對象。createRootData() 方法具體實現如下:

        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);
         // 創建RootRenderer
         const renderer = rendererFactory.createRenderer(null, null); 
         return {
         ngModule,
         injector: elInjector,
         projectableNodes,
         selectorOrNode: rootSelectorOrNode, 
         sanitizer, 
         rendererFactory, 
         renderer,
         errorHandler
         };
        }

        此時瀏覽器平臺下, Renderer 渲染器的相關基礎知識已介紹完畢。接下來,我們做一個簡單總結:

        1. Angular 應用程序啟動時會創建 RootView (生產環境下通過調用 createProdRootView() 方法)
        2. 創建 RootView 的過程中,會創建 RootData 對象,該對象可以通過 ViewData 的 root 屬性訪問到。基于 RootData 對象,我們可以通過 renderer 訪問到默認的渲染器,即 DefaultDomRenderer2 實例,此外也可以通過 rendererFactory 訪問到 RendererFactory2 實例。
        3. 在創建組件視圖 (ViewData) 時,會根據 componentRendererType 的屬性值,來設置組件關聯的 renderer 渲染器。
        4. 當渲染組件視圖的時候,Angular 會利用該組件關聯的 renderer 提供的 API,創建該視圖中的節點或執行視圖的相關操作,比如創建元素 (createElement)、創建文本 (createText)、設置樣式 (setStyle) 和 設置事件監聽 (listen) 等。

        后面如果有時間的話,我們會介紹如何自定義渲染器,有興趣的讀者,可以先查閱 "參考資源" 中的鏈接。

        聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        Angular Renderer (渲染器)的具體使用

        Angular Renderer (渲染器)的具體使用:Angular 其中的一個設計目標是使瀏覽器與 DOM 獨立。DOM 是復雜的,因此使組件與它分離,會讓我們的應用程序,更容易測試與重構。另外的好處是,由于這種解耦,使得我們的應用能夠運行在其它平臺 (比如:Node.js、WebWorkers、NativeScript 等
        推薦度:
        • 熱門焦點

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 亚洲成AV人网址| 久久久久国色AV免费观看性色| 最近免费中文字幕大全视频| 亚洲乱码精品久久久久..| 日韩国产欧美亚洲v片| 四虎www免费人成| 国产精品亚洲片夜色在线| 57pao国产成永久免费视频| 国产亚洲精品自在久久| 一级一级一片免费高清| 亚洲片一区二区三区| 国产精品亚洲二区在线| 日韩视频免费在线| 亚洲AV女人18毛片水真多| 国产成人免费片在线视频观看| 亚洲午夜成人精品无码色欲| 香蕉97超级碰碰碰免费公| 亚洲乱码无限2021芒果| 黄页网站在线看免费| 中文字幕乱码亚洲无线三区| 亚洲人成电影网站免费| 国产亚洲福利在线视频| 日本免费人成视频播放| 日韩亚洲翔田千里在线| 亚洲精品高清在线| 久久WWW免费人成—看片| 亚洲AV中文无码字幕色三| 无码人妻一区二区三区免费n鬼沢 无码人妻一区二区三区免费看 | 亚洲国产三级在线观看| 久久免费香蕉视频| 久久亚洲美女精品国产精品| 免费成人激情视频| 中文字幕乱码亚洲精品一区| 国产网站免费观看| 九九热久久免费视频| 亚洲AV永久无码精品水牛影视| 亚洲免费观看网站| 爱情岛论坛亚洲品质自拍视频网站| 亚洲国产日韩成人综合天堂 | 免费观看又污又黄在线观看| 亚洲精品综合久久|