Heim >Web-Frontend >js-Tutorial >Angular Learning: Eine kurze Analyse des inkrementellen DOM im Ivy-Compiler

Angular Learning: Eine kurze Analyse des inkrementellen DOM im Ivy-Compiler

青灯夜游
青灯夜游nach vorne
2022-02-23 11:07:132933Durchsuche

In diesem Artikel geht es darum, das Angular-Framework zu erlernen und Sie durch das inkrementelle DOM im Ivy-Compiler zu führen. Ich hoffe, es wird Ihnen hilfreich sein!

Angular Learning: Eine kurze Analyse des inkrementellen DOM im Ivy-Compiler

Als Front-End-Framework, das „für große Front-End-Projekte“ entwickelt wurde, verfügt Angular tatsächlich über viele Designs, die es wert sind, erwähnt und erlernt zu werden. Diese Serie wird hauptsächlich zum Studium der Implementierungsprinzipien dieser Designs und Funktionen verwendet. Dieser Artikel konzentriert sich auf den Ivy-Compiler, die Kernfunktion von Angular, und stellt dessen inkrementelles DOM-Design vor. [Empfohlene verwandte Tutorials: „Angular Tutorial“]

Bei der Einführung von Front-End-Frameworks führe ich häufig Template-Engines ein. Für den Rendering-Prozess von Template-Engines verwenden Frameworks wie Vue/React ein Design wie Virtual DOM.

Im Angular Ivy-Compiler wird kein virtuelles DOM verwendet, sondern inkrementelles DOM.

Inkrementelles DOM

Im Ivy-Compiler unterscheidet sich das vorlagenkompilierte Produkt von der View Engine. Dies dient der Unterstützung von Funktionen wie separater Kompilierung und inkrementeller Kompilierung.

Zum Beispiel der Vorlagencode 45a2772a6b6107b401db3c9b82c049c2My name is {{name}}54bdf357c58b8a65c66d7c19c8e4d114, der im Ivy-Compiler kompilierte Code wird wahrscheinlich so aussehen: 45a2772a6b6107b401db3c9b82c049c2My name is {{name}}54bdf357c58b8a65c66d7c19c8e4d114这句模板代码,在 Ivy 编译器中编译后的代码大概长这个样子:

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

可以看到,相比于 View Engine 中的elementDef(0,null,null,1,'span',...),elementStart()elementEnd()

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

It Es ist ersichtlich, dass im Vergleich zu elementDef(0,null,null,1,'span',...), in View Engine, elementStart(), elementEnd()Diese APIs sind sauberer und verwenden das inkrementelle DOM-Design.

Inkrementelles DOM vs. virtuelles DOM

Virtuelles DOM muss von jedem verstanden worden sein. Sein Kernberechnungsprozess umfasst:
  • Verwenden Sie JavaScript-Objekte, um den DOM-Baum zu simulieren und einen virtuellen DOM-Baum zu erhalten.
  • Wenn sich die Seitendaten ändern, wird ein neuer virtueller DOM-Baum generiert und die Unterschiede zwischen dem alten und dem neuen virtuellen DOM-Baum verglichen.
  • Wenden Sie den Unterschied auf den echten DOM-Baum an.

Obwohl virtuelles DOM die Leistungsprobleme löst, die durch häufige Seitenaktualisierungen und Renderings verursacht werden, weist herkömmliches virtuelles DOM immer noch die folgenden Leistungsengpässe auf:
  • In einer einzelnen Komponente muss immer noch der gesamte virtuelle DOM-Baum der Komponente durchlaufen werden
  • Wenn einige Komponenten nur eine kleine Anzahl dynamischer Knoten in der gesamten Vorlage haben, stellen diese Durchläufe eine Leistungsverschwendung dar
  • Rekursive Durchläufe und Aktualisierungslogik können leicht dazu führen, dass das Rendern der Benutzeroberfläche blockiert wird und die Benutzererfahrung beeinträchtigt wird

In Als Reaktion auf diese Situationen gibt es Frameworks wie React und Vue. Darüber hinaus gibt es weitere Optimierungen, z. B. die Algorithmusoptimierung für Baumdiff, Komponentendiff und Elementdiff in React, und es wird eine Aufgabenplanung eingeführt, um die Berechnung und Darstellung von Statusaktualisierungen zu steuern. In Vue 3.0 wird die Aktualisierung des virtuellen DOM vom bisherigen Gesamtumfang auf den Baumumfang angepasst. Die Baumstruktur führt zu einer Vereinfachung des Algorithmus und einer Verbesserung der Leistung.

Auf jeden Fall gibt es beim Entwurf des virtuellen DOM ein unvermeidbares Problem: Bei jedem Rendering-Vorgang wird ein neuer virtueller DOM-Baum zugewiesen, der mindestens groß genug ist, um die geänderten Knoten aufzunehmen, und normalerweise größer, so dass das Design resultiert in einem größeren Speicherbedarf. Wenn große virtuelle DOM-Bäume umfangreiche Aktualisierungen erfordern, insbesondere auf mobilen Geräten mit begrenztem Speicher, kann die Leistung darunter leiden.

Die Kernidee des inkrementellen DOM-Designs ist:
  • Wenn Sie einen neuen (virtuellen) DOM-Baum erstellen, gehen Sie am vorhandenen Baum entlang und finden Sie dabei Änderungen.
  • Wenn es keine Änderungen gibt, wird kein Speicher zugewiesen.
  • Wenn ja, ändern Sie den vorhandenen Baum (ordnen Sie Speicher nur zu, wenn dies unbedingt erforderlich ist) und wenden Sie die Differenz auf das physische DOM an.

Ich habe (virtuell) hier in Klammern gesetzt, weil beim Mischen vorberechneter Metainformationen in vorhandene DOM-Knoten die Verwendung eines physischen DOM-Baums anstelle der Verwendung eines virtuellen DOM-Baums eigentlich schnell genug ist.

Inkrementelles DOM hat zwei Hauptvorteile gegenüber virtuellen DOM-basierten Ansätzen:
  • Die inkrementelle Funktion ermöglicht eine deutlich geringere Speicherzuweisung beim Rendern, was zu einer vorhersehbareren Leistung führt.
  • Die Zuordnung zu einem vorlagenbasierten Ansatz ist einfach. Steueranweisungen und Schleifen können frei mit Element- und Attributdeklarationen gemischt werden

Das Design von inkrementellem DOM wurde von Google vorgeschlagen und sie bieten auch eine Open-Source-Bibliothek google/incremental-dom

, eine Bibliothek zum Ausdrucken und Anwenden DOM-Bäume Aktualisierte Bibliotheken. JavaScript kann zum Extrahieren, Iterieren und Konvertieren von Daten in Aufrufe verwendet werden, die HTMLElements- und Textknoten generieren.

Aber die neue Ivy-Engine nutzt es nicht direkt, sondern implementiert eine eigene Version.

Inkrementelles DOM in Ivy

Die Ivy-Engine basiert auf dem Konzept des inkrementellen DOM. Der Unterschied zur virtuellen DOM-Methode besteht darin, dass die Diff-Operation inkrementell auf dem DOM (d. h. jeweils einem Knoten) und nicht auf dem DOM ausgeführt wird virtuelles DOM. Wird im DOM-Baum ausgeführt. Basierend auf diesem Design arbeiten das inkrementelle DOM und der Dirty-Checking-Mechanismus in Angular tatsächlich gut zusammen. 🎜

增量 DOM 元素创建

增量 DOM 的 API 的一个独特功能是它分离了标签的打开(elementStart)和关闭(elementEnd),因此它适合作为模板语言的编译目标,这些语言允许(暂时)模板中的 HTML 不平衡(比如在单独的模板中,打开和关闭的标签)和任意创建 HTML 属性的逻辑。

在 Ivy 中,使用elementStartelementEnd创建一个空的 Element 实现如下(在 Ivy 中,elementStartelementEnd的具体实现便是ɵɵ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 元素的过程中,主要依赖于LViewTViewTNode

在 Angular Ivy 中,使用了LViewTView.data来管理和跟踪渲染模板所需要的内部数据。对于TNode,在 Angular 中则是用于在特定类型的所有模板之间共享的特定节点的绑定数据(享元)。

ɵɵelementEnd()则用于标记元素的结尾:

export function ɵɵelementEnd(): void {}

对于ɵɵelementEnd()的详细实现不过多介绍,基本上主要包括一些对 Class 和样式中@input等指令的处理,循环遍历提供的tNode上的指令、并将要运行的钩子排入队列,元素层次的处理等等。

组件创建与增量 DOM 指令

在增量 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);
}

可见,在组件编译时,会被编译成一系列的指令,包括constvarsdirectivespipesstyleschangeDetection等等,当然也包括template模板里的相关指令。最终生成的这些指令,会体现在编译后的组件中,比如之前文章中提到的这样一个Component文件:

import { Component, Input } from "@angular/core";

@Component({
  selector: "greet",
  template: "<div> Hello, {{name}}! </div>",
})
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,还包括变化检测等各种能力,关于具体的渲染过程,我们会在下一讲中进行介绍。

更多编程相关知识,请访问:编程教学!!

Das obige ist der detaillierte Inhalt vonAngular Learning: Eine kurze Analyse des inkrementellen DOM im Ivy-Compiler. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen