Maison > interface Web > js tutoriel > le corps du texte

Apprentissage angulaire : une brève analyse du DOM incrémental dans le compilateur Ivy

青灯夜游
Libérer: 2022-02-23 11:07:13
avant
2854 Les gens l'ont consulté

Cet article concerne l'apprentissage du framework Angular et vous guide à travers le DOM incrémentiel dans le compilateur Ivy. J'espère qu'il vous sera utile !

Apprentissage angulaire : une brève analyse du DOM incrémental dans le compilateur Ivy

En tant que framework front-end conçu "pour les projets front-end à grande échelle", Angular propose en fait de nombreuses conceptions qui méritent d'être référencées et apprises. Cette série est principalement utilisée pour étudier les principes de mise en œuvre de ces conceptions et fonctions. Cet article se concentre sur le compilateur Ivy, la fonction principale d'Angular, et présente sa conception DOM incrémentielle. [Tutoriels associés recommandés : "Tutoriel angulaire"]

Lors de l'introduction des frameworks front-end, j'introduis souvent des moteurs de modèles. Pour le processus de rendu des moteurs de modèles, des frameworks comme Vue/React utilisent des conceptions telles que le DOM virtuel.

Dans le compilateur Angular Ivy, le DOM virtuel n'est pas utilisé, mais le DOM incrémentiel est utilisé.

DOM incrémental

Dans le compilateur Ivy, le produit compilé par modèle est différent du moteur de vue. Il s'agit de prendre en charge des fonctionnalités telles que la compilation séparée et la compilation incrémentielle.

Par exemple, le code du modèle <span>Je m'appelle {{name}}</span>, le code compilé dans le compilateur Ivy ressemblera probablement à ceci : <span>My name is {{name}}</span>这句模板代码,在 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));
}
Copier après la connexion

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

export function ɵɵelement(
  index: number,
  name: string,
  attrsIndex?: number | null,
  localRefsIndex?: number
): void {
  ɵɵelementStart(index, name, attrsIndex, localRefsIndex);
  ɵɵelementEnd();
}
Copier après la connexion
Copier après la connexion

Il peut être vu que par rapport à elementDef(0,null,null,1,'span',...), dans View Engine, elementStart(), < code> elementEnd()Ces API semblent plus propres et utilisent la conception DOM incrémentielle.

DOM incrémental vs DOM virtuel

Le DOM virtuel doit avoir été compris par tout le monde. Son processus de calcul de base comprend :
  • Utilisez des objets JavaScript pour simuler l'arborescence DOM et obtenir une arborescence DOM virtuelle.
  • Lorsque les données de la page changent, une nouvelle arborescence DOM virtuelle est générée et les différences entre l'ancienne et la nouvelle arborescence DOM virtuelle sont comparées.
  • Appliquez la différence au véritable arbre DOM.

Bien que le DOM virtuel résolve les problèmes de performances causés par les mises à jour et les rendus fréquents des pages, le DOM virtuel traditionnel présente toujours les goulots d'étranglement de performances suivants :
  • Dans un seul composant, l'intégralité de l'arborescence DOM virtuel du composant doit encore être parcourue.
  • Lorsque certains composants n'ont qu'un petit nombre de nœuds dynamiques dans l'ensemble du modèle, ces traversées sont un gaspillage de performances
  • La traversée récursive et la logique de mise à jour peuvent facilement provoquer le blocage du rendu de l'interface utilisateur et la dégradation de l'expérience utilisateur

Dans En réponse à ces situations, des frameworks tels que React et Vue Il existe également davantage d'optimisations, telles que l'optimisation des algorithmes pour les différences d'arbres, les différences de composants et d'éléments dans React, et la planification des tâches est introduite pour contrôler le calcul et le rendu des mises à jour de statut. Dans Vue 3.0, la mise à jour du DOM virtuel est ajustée de la portée globale précédente à la portée arborescente. La structure arborescente entraînera une simplification de l'algorithme et une amélioration des performances.

Dans tous les cas, il existe un problème inévitable dans la conception du DOM virtuel : chaque opération de rendu alloue une nouvelle arborescence DOM virtuelle, qui est au moins suffisamment grande pour accueillir les nœuds modifiés, et généralement plus grande, comme ceci. dans une empreinte mémoire plus grande. Lorsque de grandes arborescences DOM virtuelles nécessitent des mises à jour importantes, en particulier sur les appareils mobiles disposant de peu de mémoire, les performances peuvent en souffrir.

L'idée de base de la conception du DOM incrémentiel est la suivante :
  • Lors de la création d'une nouvelle arborescence DOM (virtuelle), parcourez l'arborescence existante et trouvez les modifications au fur et à mesure.
  • S'il n'y a aucun changement, aucune mémoire n'est allouée ;
  • S'il y en a, muter l'arborescence existante (allouer de la mémoire uniquement si cela est absolument nécessaire) et appliquer la différence au DOM physique.

J'ai mis (virtuel) entre parenthèses ici car lors du mélange de méta-informations précalculées dans des nœuds DOM existants, utiliser une arborescence DOM physique au lieu de s'appuyer sur une arborescence DOM virtuelle est en fait assez rapide.

Le DOM incrémental présente deux avantages principaux par rapport aux approches basées sur le DOM virtuel :
  • La fonctionnalité incrémentielle permet d'allouer beaucoup moins de mémoire pendant le rendu, ce qui entraîne des performances plus prévisibles
  • Il est facile de mapper à une approche basée sur un modèle. Les instructions de contrôle et les boucles peuvent être librement mélangées avec les déclarations d'éléments et d'attributs

La conception du DOM incrémentiel a été proposée par Google, et ils fournissent également une bibliothèque open source google/incremental-dom

, qui est une bibliothèque pour exprimer et appliquer Arbres DOM Bibliothèques mises à jour. JavaScript peut être utilisé pour extraire, itérer et convertir des données en appels qui génèrent des nœuds HTMLElements et Text.

Mais le nouveau moteur Ivy ne l'utilise pas directement, mais implémente sa propre version.

DOM incrémental dans Ivy

Le moteur Ivy est basé sur le concept de DOM incrémentiel. La différence avec la méthode DOM virtuel est que l'opération de comparaison est effectuée de manière incrémentielle sur le DOM (c'est-à-dire un nœud à la fois), plutôt que sur le DOM incrémental. DOM virtuel Exécuté sur l'arborescence DOM. Sur la base de cette conception, le DOM incrémentiel et le mécanisme de vérification sale d'Angular fonctionnent bien ensemble. 🎜

增量 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();
}
Copier après la connexion
Copier après la connexion

其中,ɵɵ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);
  }
}
Copier après la connexion

可以看到,在ɵɵelementStart创建 DOM 元素的过程中,主要依赖于LViewTViewTNode

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

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

export function ɵɵelementEnd(): void {}
Copier après la connexion

对于ɵɵ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);
}
Copier après la connexion

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

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

@Component({
  selector: "greet",
  template: "<div> Hello, {{name}}! </div>",
})
export class GreetComponent {
  @Input() name: string;
}
Copier après la connexion

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, "!");
    }
  },
});
Copier après la connexion

其中,elementStart()text()elementEnd()advance()textInterpolate1()这些都是增量 DOM 相关的指令。在实际创建组件的时候,其template模板函数也会被执行,相关的指令也会被执行。

正因为在 Ivy 中,是由组件来引用着相关的模板指令。如果组件不引用某个指令,则我们的 Angular 中永远不会使用到它。因为组件编译的过程发生在编译过程中,因此我们可以根据引用到指令,来排除未引用的指令,从而可以在 Tree-shaking 过程中,将未使用的指令从包中移除,这便是增量 DOM 可树摇的原因。

结束语

现在,我们已经知道在 Ivy 中,是通过编译器将模板编译为template渲染函数,其中会将对模板的解析编译成增量 DOM 相关的指令。其中,在elementStart()执行时,我们可以看到会通过createElementNode()方法来创建 DOM。实际上,增量 DOM 的设计远不止只是创建 DOM,还包括变化检测等各种能力,关于具体的渲染过程,我们会在下一讲中进行介绍。

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!