OF Incremental DOM

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.

How virtual DOM works

The main concept of virtual DOM is to keep a virtual representation of the UI in memory and synchronize it with the real DOM using a reconciliation process.

The process consists of three main steps

  • When the user UI changes, render the entire user UI into the virtual DOM

  • Computes the difference between the previous virtual DOM and the current virtual DOM representation

  • Update the real DOM based on the change difference

How incremental DOM works

Incremental DOM brings a simpler approach than virtual DOM by using the real DOM to locate code changes.

Therefore, there won't be any virtual representation of the real DOM in memory to calculate the differences, the real DOM is only used for diff comparisons with the new DOM tree.

The main idea behind the incremental DOM concept is

Compile each component into a set of instructions. These directives are then used to create the DOM tree and make changes to it

  • Example

    @Component({
      selector: 'todos-cmp',
      template: `
        <div *ngFor="let t of todos|async">
            {
         
         {t.description}}
        </div>
      `
    })
    class TodosComponent {
      todos: Observable<Todo[]> = this.store.pipe(select('todos'));
      constructor(private store: Store<AppState>) {}
    }
    
    var TodosComponent = /** @class */ (function () {
      function TodosComponent(store) {
        this.store = store;
        this.todos = this.store.pipe(select('todos'));
      }
    
      TodosComponent.ngComponentDef = defineComponent({
        type: TodosComponent,
        selectors: [["todos-cmp"]],
        factory: function TodosComponent_Factory(t) {
          return new (t || TodosComponent)(directiveInject(Store));
        },
        consts: 2,
        vars: 3,
        template: function TodosComponent_Template(rf, ctx) {
          if (rf & 1) { // create dom
            pipe(1, "async");
            template(0, TodosComponent_div_Template_0, 2, 1, null, _c0);
          } if (rf & 2) { // update dom
            elementProperty(0, "ngForOf", bind(pipeBind1(1, 1, ctx.todos)));
          }
        },
        encapsulation: 2
      });
    
      return TodosComponent;
    }());
    
  • Example

    <span>My name is {
         
         {name}}</span>
    
    // 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));
    }
    

Tree Shaking characteristics

Tree Shaking refers to the process of removing unreferenced code in the context when compiling target code.

Incremental DOM takes full advantage of this because it uses a directive-based approach.

As mentioned before, incremental DOM compiles each component into a set of directives before compilation, which helps identify unused directives. Therefore, they can be removed at compile time

Virtual DOM is not capable of Tree Shaking because it uses an interpreter and there is no way to identify unused code at compile time

Reduce memory usage

Unlike virtual DOM, incremental DOM does not generate a copy of the real DOM when re-rendering the application UI.

Additionally, if there are no changes to the application UI, the delta DOM will not allocate any memory.

Most of the time, we re-render the application UI without any major modifications. Therefore, following this approach can significantly save your device’s memory usage;

Incremental DOM does not require any memory when the view does not change. We only need to allocate memory when adding or removing DOM. And the allocated memory size is proportional to the size of the changed DOM

Incremental DOM seems to have a solution for reducing virtual DOM memory footprint. But why don't other frameworks use it.

There is a trade-off here, although incremental DOM reduces memory usage by following a more efficient method of calculating differences, this method is more time-consuming than virtual DOM.

Therefore, there is a trade-off between runtime speed and memory usage when choosing between using incremental DOM and virtual DOM.

Incremental DOM in Ivy

The Ivy engine is based on the concept of incremental DOM, which differs from the virtual DOM approach in that diff operations are performed incrementally against the DOM (i.e. one node at a time) rather than on a virtual DOM tree.

Based on this design, incremental DOM actually works well with the dirty checking mechanism in Angular.

Incremental DOM element creation

A unique feature of the incremental DOM API is that it separates the opening (elementStart) and closing (elementEnd) of the tag, so it is suitable as Compilation targets for template languages ​​that allow (temporary) imbalance of HTML in templates (such as opening and closing tags in separate templates) and arbitrary creation of logic for HTML attributes.

In Ivy, use elementStart and elementEnd to create an empty Element as follows (in Ivy, elementStart The specific implementation of and elementEnd is ɵɵelementStart and ɵɵelementEnd):

export function ɵɵelement(
  index: number,
  name: string,
  attrsIndex?: number | null,
  localRefsIndex?: number
): void {
  ɵɵelementStart(index, name, attrsIndex, localRefsIndex);
  ɵɵelementEnd();
}

ɵɵelementStart is used to create a DOM element. This instruction must be followed by a ɵɵelementEnd() call.

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);
  }
}

In the process of ɵɵelementStart creating DOM elements, it mainly relies on LView, TView and < a i=4>. TNode

In Angular Ivy, LView and TView.data are used to manage and track the internal data required to render templates.

ForTNode, in Angular it is the binding data (flyweight) for a specific node that is shared between all templates of a specific type.

ɵɵelementEnd()is used to mark the end of an element:

export function ɵɵelementEnd(): void {}

The detailed implementation ofɵɵelementEnd() will not be introduced too much. Basically, it mainly includes the processing of instructions such as @input in Class and style, and the loop traversal provides tNode, queuing the hook to be run, element-level processing, etc.

Component creation and incremental DOM directives

In incremental DOM, each component is compiled into a series of instructions. These directives create DOM trees and update them in-place when data changes.

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);
}

will be compiled into a series of instructions when compiling the component, including const, vars, directives, < /span> template. The final generated instructions will be reflected in the compiled components. , etc., including of course the relevant instructions in the , pipes, styleschangeDetectiontemplate

  • Example

    import { Component, Input } from "@angular/core";
    
    @Component({
      selector: "greet",
      template: "<div> Hello, {
         
         {name}}! </div>",
    })
    export class GreetComponent {
      @Input() name: string;
    }
    

    Afterngtsccompilation, the product includes the .jsfile of the component

    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, "!");
        }
      },
    });
    

    Among them, elementStart(), text(), elementEnd(), advance(), textInterpolate1()These are instructions related to incremental DOM. When the component is actually created, its template function will also be executed, and related instructions will also be executed. template

    In Ivy, components reference related template directives. If a component doesn't reference a directive, it will never be used in our Angular.

    Because the component compilation process occurs during the compilation process, we can exclude unreferenced instructions based on the referenced instructions, so that unused instructions can be removed from the package during the tree-shaking process. This is the increase Measuring the DOM can cause tree shaking.

Advantages and Disadvantages of Virtual DOM

  • Efficient diff algorithm
  • Simple and helps improve performance
  • Works without React
  • Lightweight
  • Allows applications to be built regardless of state transitions

Although virtual DOM is fast and efficient, it has a drawback

This diffing process really reduces the workload of the real DOM. But it requires comparing the current virtual DOM state with the previous state to identify changes. To understand this better, let’s look at a small React code example

function WelcomeMessage (props) {
  return (
    <div className="welcome">
      Welcome {props.name}
    </div>
  );
}

Assume that the initial value of props.name is Chameera and is later changed to Reader.

The only change in the entire code is the props. There is no need to change the DOM node or compare the properties inside the <div> tag.

However, with the diff algorithm, it is necessary to check all steps to identify changes.

We see a lot of these small changes during development, and comparing every element in the user UI is undoubtedly an overhead.

There is an unavoidable problem in the design of virtual DOM:

Each rendering operation allocates a new virtual DOM tree that is at least large enough to accommodate the changed nodes, and usually larger, resulting in a larger memory footprint.

When large virtual DOM trees require heavy updates, especially on memory-constrained mobile devices, performance can suffer.

This can be considered one of the major disadvantages of Virtual DOM, however, Delta DOM provides a solution to this heavy memory usage problem

In React, tree diff, component diff and element diff are algorithmically optimized respectively, and task scheduling is introduced to control the calculation and rendering of status 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.

Advantages and Disadvantages of Incremental DOM

As mentioned earlier, Delta DOM provides a solution to reduce virtual DOM memory consumption by using the real DOM to track changes.

This approach greatly reduces computational overhead and also optimizes application memory usage.

So, this is the main advantage of using incremental DOM over virtual DOM, one can list other advantages of incremental DOM

  • Easy to use with many other frameworks
  • Simple API makes it a powerful target template engine
  • Suitable for mobile device based applications,
  • Optimized memory usage

In most cases, incremental DOM does not run as fast as virtual DOM

Although incremental DOM brings a solution to reduce memory usage, this solution affects the speed of incremental DOM because the difference calculation of incremental DOM takes more time than the virtual DOM approach.

Therefore, we can consider this the main disadvantage of using incremental DOM.

How to apply incremental DOM in Angular Ivy compiler - Qunying

Understanding Angular Ivy: Incremental DOM and Virtual DOM

Incremental DOM and virtual DOM

Learn about Angular Ivy: Incremental DOM and Virtual DOM

Guess you like

Origin blog.csdn.net/SeriousLose/article/details/128130309