This article is to learn theAngularframework and let you know about the incremental DOM in the Ivy compiler. I hope it will be helpful to everyone!
As a front-end framework designed “for large-scale front-end projects”, Angular actually has many designs worthy of reference and learning. This series is mainly used to study these designs and functions. Realization principle. This article focuses on the Ivy compiler, the core function of Angular, and introduces its incremental DOM design. [Related tutorial recommendations: "angular tutorial"]
When introducing the front-end framework, I often introduce the template engine. For the rendering process of template engines, frameworks like Vue/React use designs such as virtual DOM.
In the Angular Ivy compiler, virtual DOM is not used, but incremental DOM is used.
In the Ivy compiler, the template compiled product is different from the View Engine. This is to support capabilities such as separate compilation and incremental compilation.
For example,My name is {{name}}
This template code, the code compiled in the Ivy compiler will probably look like this:
// create mode if (rf & RenderFlags.Create) { elementStart(0, "span"); text(1); elementEnd(); } // update mode if (rf & RenderFlags.Update) { textBinding(1, interpolation1("My name is", ctx.name)); }
You can see that compared toelementDef(0,null,null,1,'span',...),
,elementStart() in View Engine
,elementEnd()
These APIs appear more refreshing, and they use the incremental DOM design.
Virtual DOM must have been understood by everyone. Its core calculation process includes:
Use JavaScript objects Simulate the DOM tree and get a virtual DOM tree.
When the page data changes, a new virtual DOM tree is generated and the differences between the old and new virtual DOM trees are compared.
Apply the difference to the real DOM tree.
Although virtual DOM solves the performance problems caused by frequent page updates and renderings, traditional virtual DOM still has the following performance bottlenecks:
In view of these situations, frameworks such as React and Vue also have more optimizations. For example, React has algorithms for tree diff, component diff and element diff respectively. Optimization, while introducing task scheduling to control the calculation and rendering of state updates. In Vue 3.0, the update of the virtual DOM is adjusted from the previous overall scope to the tree scope. The tree structure will bring about simplification of the algorithm and improvement of performance.
In any case, there is an unavoidable problem in the design of virtual DOM: each rendering operation allocates a new virtual DOM tree, which is at least large enough to accommodate the changed nodes, and is usually larger Some, such a design will result in more memory usage. When large virtual DOM trees require heavy updates, especially on memory-constrained mobile devices, performance can suffer.
The core idea of incremental DOM design is:
When creating a new (virtual) DOM tree, walk along the existing tree, and Find the changes.
If there are no changes, no memory is allocated;
If there are, change the existing tree (allocate memory only when absolutely necessary) and Apply the difference to the physical DOM.
The (virtual) is put in parentheses here because when mixing precomputed meta information into existing DOM nodes, the physical DOM tree is used instead of relying on the virtual DOM tree. It's actually fast enough.
Incremental DOM has two main advantages compared to virtual DOM-based approaches:
The design of incremental DOM was proposed by Google, and they also provide an open source librarygoogle/incremental-dom, It is a library for expressing and applying DOM tree updates. JavaScript can be used to extract, iterate, and convert data into calls that generate HTMLElements and Text nodes.
But the new Ivy engine does not use it directly, but implements its own version.
The Ivy engine is based on the concept of incremental DOM. The difference from the virtual DOM method is that the diff operation is performed incrementally against the DOM (i.e. once node) instead of executing on the virtual DOM tree. Based on this design, incremental DOM actually works well with the dirty checking mechanism in Angular.
增量 DOM 的 API 的一个独特功能是它分离了标签的打开(elementStart
)和关闭(elementEnd
),因此它适合作为模板语言的编译目标,这些语言允许(暂时)模板中的 HTML 不平衡(比如在单独的模板中,打开和关闭的标签)和任意创建 HTML 属性的逻辑。
在 Ivy 中,使用elementStart
和elementEnd
创建一个空的 Element 实现如下(在 Ivy 中,elementStart
和elementEnd
的具体实现便是ɵɵelementStart
和ɵɵelementEnd
):
export function ɵɵelement( index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number ): void { ɵɵelementStart(index, name, attrsIndex, localRefsIndex); ɵɵelementEnd(); }
其中,ɵɵelementStart
用于创建 DOM 元素,该指令后面必须跟有ɵɵelementEnd()
调用。
export function ɵɵelementStart( index: number, name: string, attrsIndex?: number | null, localRefsIndex?: number ): void { const lView = getLView(); const tView = getTView(); const adjustedIndex = HEADER_OFFSET + index; const renderer = lView[RENDERER]; // 此处创建 DOM 元素 const native = (lView[adjustedIndex] = createElementNode( renderer, name, getNamespace() )); // 获取 TNode // 在第一次模板传递中需要收集匹配 const tNode = tView.firstCreatePass ? elementStartFirstCreatePass( adjustedIndex, tView, lView, native, name, attrsIndex, localRefsIndex) : tView.data[adjustedIndex] as TElementNode; setCurrentTNode(tNode, true); const mergedAttrs = tNode.mergedAttrs; // 通过推断的渲染器,将所有属性值分配给提供的元素 if (mergedAttrs !== null) { setUpAttributes(renderer, native, mergedAttrs); } // 将 className 写入 RElement const classes = tNode.classes; if (classes !== null) { writeDirectClass(renderer, native, classes); } // 将 cssText 写入 RElement const styles = tNode.styles; if (styles !== null) { writeDirectStyle(renderer, native, styles); } if ((tNode.flags & TNodeFlags.isDetached) !== TNodeFlags.isDetached) { // 添加子元素 appendChild(tView, lView, native, tNode); } // 组件或模板容器的任何直接子级,必须预先使用组件视图数据进行猴子修补 // 以便稍后可以使用任何元素发现实用程序方法检查元素 if (getElementDepthCount() === 0) { attachPatchData(native, lView); } increaseElementDepthCount(); // 对指令 Host 的处理 if (isDirectiveHost(tNode)) { createDirectivesInstances(tView, lView, tNode); executeContentQueries(tView, tNode, lView); } // 获取本地名称和索引的列表,并将解析的本地变量值按加载到模板中的相同顺序推送到 LView if (localRefsIndex !== null) { saveResolvedLocalsInData(lView, tNode); } }
可以看到,在ɵɵelementStart
创建 DOM 元素的过程中,主要依赖于LView
、TView
和TNode
。
在 Angular Ivy 中,使用了LView
和TView.data
来管理和跟踪渲染模板所需要的内部数据。对于TNode
,在 Angular 中则是用于在特定类型的所有模板之间共享的特定节点的绑定数据(享元)。
ɵɵelementEnd()
则用于标记元素的结尾:
export function ɵɵelementEnd(): void {}
对于ɵɵelementEnd()
的详细实现不过多介绍,基本上主要包括一些对 Class 和样式中@input
等指令的处理,循环遍历提供的tNode
上的指令、并将要运行的钩子排入队列,元素层次的处理等等。
在增量 DOM 中,每个组件都被编译成一系列指令。这些指令创建 DOM 树并在数据更改时就地更新它们。
Ivy 在运行时编译一个组件的过程中,会创建模板解析相关指令:
export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser ): R3ComponentDef { // 其他暂时省略 // 创建一个 TemplateDefinitionBuilder,用于创建模板相关的处理 const templateBuilder = new TemplateDefinitionBuilder( constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, directiveMatcher, directivesUsed, meta.pipes, pipesUsed, R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds); // 创建模板解析相关指令,包括: // 第一轮:创建模式,包括所有创建模式指令(例如解析侦听器中的绑定) // 第二轮:绑定和刷新模式,包括所有更新模式指令(例如解析属性或文本绑定) const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); // 提供这个以便动态生成的组件在实例化时,知道哪些投影内容块要传递给组件 const ngContentSelectors = templateBuilder.getNgContentSelectors(); if (ngContentSelectors) { definitionMap.set("ngContentSelectors", ngContentSelectors); } // 生成 ComponentDef 的 consts 部分 const { constExpressions, prepareStatements } = templateBuilder.getConsts(); if (constExpressions.length > 0) { let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions); // 将 consts 转换为函数 if (prepareStatements.length > 0) { constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]); } definitionMap.set("consts", constsExpr); } // 生成 ComponentDef 的 template 部分 definitionMap.set("template", templateFunctionExpression); }
可见,在组件编译时,会被编译成一系列的指令,包括const
、vars
、directives
、pipes
、styles
、changeDetection
等等,当然也包括template
模板里的相关指令。最终生成的这些指令,会体现在编译后的组件中,比如之前文章中提到的这样一个Component
文件:
import { Component, Input } from "@angular/core"; @Component({ selector: "greet", template: "Hello, {{name}}!", }) export class GreetComponent { @Input() name: string; }
经ngtsc
编译后,产物包括该组件的.js
文件:
const i0 = require("@angular/core"); class GreetComponent {} GreetComponent.ɵcmp = i0.ɵɵdefineComponent({ type: GreetComponent, tag: "greet", factory: () => new GreetComponent(), template: function (rf, ctx) { if (rf & RenderFlags.Create) { i0.ɵɵelementStart(0, "div"); i0.ɵɵtext(1); i0.ɵɵelementEnd(); } if (rf & RenderFlags.Update) { i0.ɵɵadvance(1); i0.ɵɵtextInterpolate1("Hello ", ctx.name, "!"); } }, });
其中,elementStart()
、text()
、elementEnd()
、advance()
、textInterpolate1()
这些都是增量 DOM 相关的指令。在实际创建组件的时候,其template
模板函数也会被执行,相关的指令也会被执行。
正因为在 Ivy 中,是由组件来引用着相关的模板指令。如果组件不引用某个指令,则我们的 Angular 中永远不会使用到它。因为组件编译的过程发生在编译过程中,因此我们可以根据引用到指令,来排除未引用的指令,从而可以在 Tree-shaking 过程中,将未使用的指令从包中移除,这便是增量 DOM 可树摇的原因。
现在,我们已经知道在 Ivy 中,是通过编译器将模板编译为template
渲染函数,其中会将对模板的解析编译成增量 DOM 相关的指令。其中,在elementStart()
执行时,我们可以看到会通过createElementNode()
方法来创建 DOM。实际上,增量 DOM 的设计远不止只是创建 DOM,还包括变化检测等各种能力,关于具体的渲染过程,我们会在下一讲中进行介绍。
更多编程相关知识,请访问:编程教学!!
The above is the detailed content of Angular learning: a brief analysis of the incremental DOM in the Ivy compiler. For more information, please follow other related articles on the PHP Chinese website!