Maison  >  Article  >  interface Web  >  [Français] Mécanisme de mise à jour du DOM angulaire - Partage de technologie Laravel/Angular

[Français] Mécanisme de mise à jour du DOM angulaire - Partage de technologie Laravel/Angular

寻∝梦
寻∝梦original
2018-09-07 16:24:051286parcourir
Cet article vous présente principalement le mécanisme de mise à jour de angularjs dom, ainsi que de nombreux points de connaissances sur les expressions du modèle d'angularjs et l'architecture interne du programme

expression du modèle angulaire js :

La mise à jour du DOM déclenchée par les modifications du modèle est une fonction importante de tous les frameworks front-end (remarque : garder le modèle et la vue synchronisés), bien sûr, Angular ne fait pas exception. Définissez une expression de modèle comme suit :

<span>Hello {{name}}</span>
ou une liaison de propriété similaire à la suivante (remarque : ceci est équivalent au code ci-dessus) :

<span [textContent]="&#39;Hello &#39; + name"></span>
lorsque chaque

value Lorsque des changements se produisent, Angular mettra automatiquement à jour comme par magie les éléments DOM (Remarque : le code supérieur met à jour le namenœud de texte DOM , et le code ci-dessus met à jour le nœud d'élément DOM . Les deux sont différent, expliqué ci-dessous). Cela semble simple à première vue, mais son fonctionnement interne est assez complexe. De plus, les mises à jour du DOM ne sont qu'une partie du mécanisme de détection des changements d'Angular. Le mécanisme de détection des changements comprend principalement les trois étapes suivantes :

  • Mises à jour du DOM (Remarque : cet article). expliquera le contenu)

  • composants enfants

    mises à jour des liaisonsInput

  • mises à jour de la liste de requêtes

Cet article explore principalement la partie rendu du mécanisme de détection des changements (c'est-à-dire la partie mises à jour du DOM). Si vous avez déjà été curieux de connaître ce problème, vous pouvez continuer à lire et cela vous éclairera certainement.

Lorsque vous citez le code source pertinent, il est supposé que le programme s'exécute en mode production. Commençons !

Structure interne du programme

Avant d'explorer les mises à jour du DOM, voyons d'abord comment le programme Angular est conçu en interne. Examinons-le brièvement.

Vue

De mon article

Voici ce qu'il faut savoir sur les composants dynamiques dans AngularSachez que le compilateur Angular compilera les composants utilisés dans le programme dans Une usine classe. Par exemple, le code suivant montre comment Angular crée un composant à partir d'une classe d'usine (Remarque : la logique de l'auteur semble ici un peu déroutante. La classe d'usine compilée par le compilateur Angular mentionné dans la phrase précédente est en fait effectuée par le compilateur et ne le fait pas. exiger que le développeur fasse n'importe quoi. Les choses sont des choses automatisées et le code suivant explique comment les développeurs peuvent manuellement créer une instance de composant via ComponentFactory. il veut dire les composants. Comment il est instancié) :

const factory = r.resolveComponentFactory(AComponent);
componentRef: ComponentRef<AComponent> = factory.create(injector);
Angular utilise cette classe d'usine pour instancier la

View Definition, puis utilise la fonction viewDef. pour créer la vue . Angular considère en interne un programme comme une arborescence de vues. Bien qu'un programme comporte de nombreux composants, il possède une interface de définition de vue commune pour définir la structure de vue générée par les composants (Remarque : à savoir ViewDefinition Interface Bien sûr, Angular). utilise chaque objet composant pour créer une vue correspondante, formant ainsi une arborescence de vues à partir de plusieurs vues. (Remarque : l'un des concepts principaux ici est View, et sa structure est ViewDefinition Interface)

Component Factory

La plupart du code de la fabrique de composants est composée de différents nœuds de vue générés par le compilateur. Ces nœuds de vue sont générés via l'analyse de modèles (Remarque : la fabrique de composants générée par le compilateur est une fonction qui renvoie une fonction. La ComponentFactory ci-dessus est une classe fournie. par Angular pour les appels manuels. Bien sûr, les deux pointent vers la même chose, mais sous des formes différentes). Supposons que le modèle définissant un composant est le suivant :

<span>I am {{name}}</span>
Le compilateur analysera ce modèle pour générer un code de fabrique de composants similaire au suivant (remarque : il ne s'agit que de la partie la plus importante du code) :

function View_AComponent_0(l) {
    return jit_viewDef1(0,
        [
          jit_elementDef2(0,null,null,1,'span',...),
          jit_textDef3(null,['I am ',...])
        ], 
        null,
        function(_ck,_v) {
            var _co = _v.component;
            var currVal_0 = _co.name;
            _ck(_v,1,0,currVal_0);
Remarque : Le code complet de la fonction factory compilée par le composant AppComponent est le suivant
 (function(jit_createRendererType2_0,jit_viewDef_1,jit_elementDef_2,jit_textDef_3) {
     var styles_AppComponent = [''];
     var RenderType_AppComponent = jit_createRendererType2_0({encapsulation:0,styles:styles_AppComponent,data:{}});
     function View_AppComponent_0(_l) {
         return jit_viewDef_1(0,
            [
                (_l()(),jit_elementDef_2(0,0,null,null,1,'span',[],null,null,null,null,null)),
                (_l()(),jit_textDef_3(1,null,['I am ','']))
            ],
            null,
            function(_ck,_v) {
                var _co = _v.component;
                var currVal_0 = _co.name;
                _ck(_v,1,0,currVal_0);
           });
    }
 return {RenderType_AppComponent:RenderType_AppComponent,View_AppComponent_0:View_AppComponent_0};})
Le code ci-dessus décrit la structure de la vue et sera appelé lorsque le Le composant est instancié.

est en fait la fonction jit_viewDef_1viewDef, utilisée pour créer des vues (remarque : la fonction est très importante, car la vue est créée en l'appelant, et la structure de vue générée est viewDefViewDefinition ) .

viewDef Le deuxième paramètre de la fonction nodes est un peu similaire à la signification des nœuds en html, mais c'est plus que cela. Le deuxième paramètre dans le code ci-dessus est un tableau, le premier élément du tableau est la définition du nœud d'élément et le deuxième élément du tableau jit_elementDef_2 est la définition du nœud de texte. Le compilateur Angular génère de nombreuses définitions de nœuds différentes et le type de nœud est défini par jit_textDef_3NodeFlags. Plus tard, nous verrons comment Angular effectue les mises à jour du DOM en fonction de différents types de nœuds.

Cet article ne s'intéresse qu'aux éléments et nœuds de texte :

export const enum NodeFlags {
    TypeElement = 1 << 0, 
    TypeText = 1 << 1
Parcourons-les brièvement.

注:上文作者说了一大段,其实核心就是,程序是一堆视图组成的,而每一个视图又是由不同类型节点组成的。而本文只关心元素节点和文本节点,至于还有个重要的指令节点在另一篇文章。

元素节点的结构定义

元素节点结构 是 Angular 编译每一个 html 元素生成的节点结构,它也是用来生成组件的,如对这点感兴趣可查看 Here is why you will not find components inside Angular。元素节点也可以包含其他元素节点和文本节点作为子节点,子节点数量是由 childCount 设置的。

所有元素定义是由 elementRef 函数生成的,而工厂函数中的 jit_elementDef_2() 就是这个函数。elementRef() 主要有以下几个一般性参数:

Name Description
childCount specifies how many children the current element have
namespaceAndName the name of the html element(注:如 'span')
fixedAttrs attributes defined on the element

还有其他的几个具有特定性能的参数:

Name Description
matchedQueriesDsl used when querying child nodes
ngContentIndex used for node projection
bindings used for dom and bound properties update
outputs, handleEvent used for event propagation

本文主要对 bindings 感兴趣。

注:从上文知道视图(view)是由不同类型节点(nodes)组成的,而元素节点(element nodes)是由 elementRef 函数生成的,元素节点的结构是由 ElementDef 定义的。

文本节点的结构定义

文本节点结构 是 Angular 编译每一个 html 文本 生成的节点结构。通常它是元素定义节点的子节点,就像我们本文的示例那样(注:<span>I am {{name}}</span>span 是元素节点,I am {{name}} 是文本节点,也是 span 的子节点)。这个文本节点是由 textDef 函数生成的。它的第二个参数以字符串数组形式传进来(注: Angular v5.* 是第三个参数)。例如,下面的文本:

<h1>Hello {{name}} and another {{prop}}</h1>

将要被解析为一个数组:

["Hello ", " and another ", ""]

然后被用来生成正确的绑定:

{
  text: 'Hello',
  bindings: [
    {
      name: 'name',
      suffix: ' and another '
    },
    {
      name: 'prop',
      suffix: ''
    }
  ]
}

在脏检查(注:即变更检测)阶段会这么用来生成文本:

text
+ context[bindings[0][property]] + context[bindings[0][suffix]]
+ context[bindings[1][property]] + context[bindings[1][suffix]]
注:同上,文本节点是由 textDef 函数生成的,结构是由 TextDef 定义的。既然已经知道了两个节点的定义和生成,那节点上的属性绑定, Angular 是怎么处理的呢?

节点的绑定

Angular 使用 BindingDef 来定义每一个节点的绑定依赖,而这些绑定依赖通常是组件类的属性。在变更检测时 Angular 会根据这些绑定来决定如何更新节点和提供上下文信息。具体哪一种操作是由 BindingFlags 决定的,下面列表展示了具体的 DOM 操作类型:

Name Construction in template
TypeElementAttribute attr.name
TypeElementClass class.name
TypeElementStyle style.name

元素和文本定义根据这些编译器可识别的绑定标志位,内部创建这些绑定依赖。每一种节点类型都有着不同的绑定生成逻辑(注:意思是 Angular 会根据 BindingFlags 来生成对应的 BindingDef)。(想看更多就到PHP中文网AngularJS开发手册中学习)

更新渲染器

最让我们感兴趣的是 jit_viewDef_1 中最后那个参数:

function(_ck,_v) {
   var _co = _v.component;
   var currVal_0 = _co.name;
   _ck(_v,1,0,currVal_0);
});

这个函数叫做 updateRenderer。它接收两个参数:_ck_v_ckcheck 的简写,其实就是 prodCheckAndUpdateNode 函数,而 _v 就是当前视图对象。updateRenderer 函数会在 每一次变更检测时 被调用,其参数 _ck_v 也是这时被传入。

updateRenderer 函数逻辑主要是,从组件对象的绑定属性获取当前值,并调用 _ck 函数,同时传入视图对象、视图节点索引和绑定属性当前值。重要一点是 Angular 会为每一个视图执行 DOM 更新操作,所以必须传入视图节点索引参数(注:这个很好理解,上文说了 Angular 会依次对每一个 view 做模型视图同步过程)。你可以清晰看到 _ck 参数列表:

function prodCheckAndUpdateNode(
    view: ViewData, 
    nodeIndex: number, 
    argStyle: ArgumentType, 
    v0?: any, 
    v1?: any, 
    v2?: any,

nodeIndex 是视图节点的索引,如果你模板中有多个表达式:

<h1>Hello {{name}}</h1>
<h1>Hello {{age}}</h1>

编译器生成的 updateRenderer 函数如下:

var _co = _v.component;

// here node index is 1 and property is `name`
var currVal_0 = _co.name;
_ck(_v,1,0,currVal_0);

// here node index is 4 and bound property is `age`
var currVal_1 = _co.age;
_ck(_v,4,0,currVal_1);

更新 DOM

现在我们已经知道 Angular 编译器生成的所有对象(注:已经有了 view,element node,text node 和 updateRenderer 这几个道具),现在我们可以探索如何使用这些对象来更新 DOM。

从上文我们知道变更检测期间 updateRenderer 函数传入的一个参数是 _ck 函数,而这个函数就是 prodCheckAndUpdateNode。这个函数在继续执行后,最终会调用 checkAndUpdateNodeInline ,如果绑定属性的数量超过 10,Angular 还提供了 checkAndUpdateNodeDynamic 这个函数(注:两个函数本质一样)。

checkAndUpdateNodeInline 函数会根据不同视图节点类型来执行对应的检查更新函数:

case NodeFlags.TypeElement   -> checkAndUpdateElementInline
case NodeFlags.TypeText      -> checkAndUpdateTextInline
case NodeFlags.TypeDirective -> checkAndUpdateDirectiveInline

让我们看下这些函数是做什么的,至于 NodeFlags.TypeDirective 可以查看我写的文章 The mechanics of property bindings update in Angular

注:因为本文只关注 element node 和 text node

元素节点

对于元素节点,会调用函数 checkAndUpdateElementInline 以及 checkAndUpdateElementValuecheckAndUpdateElementValue 函数会检查绑定形式是否是 [attr.name, class.name, style.some] 或是属性绑定形式:

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;

然后使用渲染器对应的方法来对该节点执行对应操作,比如使用 setElementClass 给当前节点 span 添加一个 class

文本节点

对于文本节点类型,会调用 checkAndUpdateTextInline ,下面是主要部分:

if (checkAndUpdateBinding(view, nodeDef, bindingIndex, newValue)) {
    value = text + _addInterpolationPart(...);
    view.renderer.setValue(DOMNode, value);
}

它会拿到 updateRenderer 函数传过来的当前值(注:即上文的 _ck(_v,4,0,currVal_1);),与上一次变更检测时的值相比较。视图数据包含有 oldValues 属性,如果属性值如 name 发生变化,Angular 会使用最新 name 值合成最新的字符串文本,如 Hello New World,然后使用渲染器更新 DOM 上对应的文本。(想看更多就到PHP中文网AngularJS开发手册中学习)

注:更新元素节点和文本节点都提到了渲染器(renderer),这也是一个重要的概念。每一个视图对象都有一个 renderer 属性,即是 Renderer2 的引用,也就是组件渲染器,DOM 的实际更新操作由它完成。因为 Angular 是跨平台的,这个 Renderer2 是个接口,这样根据不同 Platform 就选择不同的 Renderer。比如,在浏览器里这个 Renderer 就是 DOMRenderer,在服务端就是 ServerRenderer,等等。从这里可看出,Angular 框架设计做了很好的抽象。

结论

我知道有大量难懂的信息需要消化,但是只要理解了这些知识,你就可以更好的设计程序或者去调试 DOM 更新相关的问题。我建议你按照本文提到的源码逻辑,使用调试器或 debugger 语句 一步步去调试源码。

D'accord, cet article se termine ici (si vous voulez en savoir plus, rendez-vous sur le site Web PHP chinois Manuel d'utilisation d'AngularJS pour en savoir plus). Si vous avez des questions, vous pouvez laisser un message ci-dessous.

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!

Déclaration:
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