Dieses Mal werde ich Ihnen eine detaillierte Erklärung zur Verwendung des Angular Renderer-Renderers geben. Was sind die Vorsichtsmaßnahmen bei der Verwendung des Angular Renderer-Renderers?
Eines der Designziele von Angular ist es, den Browser unabhängig vom DOM zu machen. Das DOM ist komplex, daher erleichtert die Trennung von Komponenten das Testen und Refactoring unserer Anwendungen. Ein weiterer Vorteil besteht darin, dass unsere Anwendungen aufgrund dieser Entkopplung auf anderen Plattformen (wie Node.js, WebWorkers, NativeScript usw.) laufen können.
Um plattformübergreifend zu unterstützen, kapselt Angular die Unterschiede verschiedener Plattformen durch Abstraktionsschichten. Beispielsweise sind abstrakte Klasse Renderer, Renderer2, abstrakte Klasse RootRenderer usw. definiert. Darüber hinaus sind folgende Referenztypen definiert: ElementRef, TemplateRef, ViewRef, ComponentRef, ViewContainerRef usw.
Der Hauptinhalt dieses Artikels besteht darin, den Renderer in Angular zu analysieren. Bevor wir jedoch die spezifische Analyse durchführen, stellen wir zunächst das Konzept der Plattform vor.
Plattform
Was ist eine Plattform
Eine Plattform ist die Umgebung, in der Anwendungen ausgeführt werden . Dabei handelt es sich um eine Reihe von Diensten, mit denen Sie auf die integrierte Funktionalität Ihrer Anwendung und des Angular-Frameworks selbst zugreifen können. Da es sich bei Angular in erster Linie um ein UI-Framework handelt, ist das Rendern von Seiten eine der wichtigsten Funktionen der Plattform.
Plattform und Bootstrap-Anwendung
Bevor wir mit der Erstellung eines benutzerdefinierten Renderers beginnen, werfen wir einen Blick darauf, wie die Plattform eingerichtet und die Anwendung gebootet wird.
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {BrowserModule} from '@angular/platform-browser'; @NgModule({ imports: [BrowserModule], bootstrap: [AppCmp] }) class AppModule {} platformBrowserDynamic().bootstrapModule(AppModule);
Wie Sie sehen können, besteht der Bootstrapping-Prozess aus zwei Teilen: dem Erstellen der Plattform und dem Bootstrapping des Moduls. In diesem Beispiel importieren wir das BrowserModule-Modul, das Teil der Browserplattform ist. Es kann nur eine aktivierte Plattform in der Anwendung geben, aber wir können sie zum Bootstrapping mehrerer Module verwenden, wie unten gezeigt:
const platformRef: PlatformRef = platformBrowserDynamic(); platformRef.bootstrapModule(AppModule1); platformRef.bootstrapModule(AppModule2);
Da es in der Anwendung nur eine aktivierte Plattform geben kann, muss der Singleton-Dienst vorhanden sein Registrieren Sie sich auf der Plattform. Beispielsweise verfügt der Browser nur über eine Adressleiste und das entsprechende Dienstobjekt ist ein Singleton. Darüber hinaus erfordert die Anzeige unserer benutzerdefinierten Benutzeroberfläche im Browser die Verwendung des von Angular bereitgestellten Renderers.
Renderer
Was ist ein Renderer?
Der Renderer wird von Angular für uns bereitgestellt A Integrierter Dienst, der UI-Rendering-Vorgänge ausführt. Beim Rendern in einem Browser wird ein Modell einer Ansicht zugeordnet. Modellwerte können primitive Datentypen in JavaScript, Objekte, Arrays oder andere Datenobjekte sein. Ansichten können jedoch auch andere Elemente wie Absätze, Formulare, Schaltflächen usw. auf der Seite sein. Diese Seitenelemente werden intern durch DOM (Document Object Model) dargestellt.
Angular Renderer
RootRendererexport abstract class RootRenderer { abstract renderComponent(componentType: RenderComponentType): 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; // ... }
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; }
anstelle von Renderer2
. Durch die Beobachtung der mit Renderer verbundenen abstrakten Klassen (Renderer, Renderer2) haben wir festgestellt, dass in der abstrakten Klasse viele abstrakte Methoden definiert sind, die zum Erstellen von Elementen, Text, Festlegen von Attributen, Hinzufügen von Stilen und Festlegen der Ereignisüberwachung usw. verwendet werden. Renderer
Funktionsweise von Renderern
Beim Instanziieren einer Komponente ruft Angular die Methode auf und ordnet den erhaltenen Renderer der Komponenteninstanz zu. Angular führt beim Rendern der Komponente entsprechende Vorgänge über den Renderer aus, z. B. das Erstellen von Elementen, das Festlegen von Eigenschaften, das Hinzufügen von Stilen und das Abonnieren von Ereignissen. renderComponent()
Renderer verwenden
@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'); } }
// 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);
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; } } } }
上面代码中的 EmulatedEncapsulationDomRenderer2
和 ShadowDomRenderer
类都继承于 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; } }
介绍完 DomRendererFactory2
和 DefaultDomRenderer2
类,最后我们来看一下 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
属性值,来创建组件的渲染器。接下来我们先来看一下 NodeDef
、 ElementDef
和 RendererType2
接口定义:
// 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; }
那好,现在问题来了:
什么时候创建 RootData
对象?
怎么创建 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
渲染器的相关基础知识已介绍完毕。接下来,我们做一个简单总结:
Angular 应用程序启动时会创建 RootView (生产环境下通过调用 createProdRootView() 方法)
创建 RootView 的过程中,会创建 RootData 对象,该对象可以通过 ViewData 的 root 属性访问到。基于 RootData 对象,我们可以通过 renderer
访问到默认的渲染器,即 DefaultDomRenderer2 实例,此外也可以通过 rendererFactory
访问到 RendererFactory2
实例。
在创建组件视图 (ViewData) 时,会根据 componentRendererType
的属性值,来设置组件关联的 renderer
渲染器。
Beim Rendern einer Komponentenansicht verwendet Angular die von renderer
bereitgestellte API, die mit der Komponente verknüpft ist, um Knoten in der Ansicht zu erstellen oder ansichtsbezogene Vorgänge auszuführen, z. B. das Erstellen von Elementen (createElement). Erstellen Sie Text (createText), legen Sie den Stil fest (setStyle) und legen Sie die Ereignisüberwachung fest (listen) usw.
Ich glaube, dass Sie die Methode beherrschen, nachdem Sie den Fall in diesem Artikel gelesen haben. Weitere spannende Informationen finden Sie in anderen verwandten Artikeln auf der chinesischen PHP-Website!
Empfohlene Lektüre:
Detaillierte Erläuterung der Vor- und Nachteile der Verwendung von Webpack Path und publicPath
JS+HTML5 zu Erstellen Sie mausgebundene Partikel. Streamen Sie Animationen
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Verwendung von Angular Renderer. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!