Angular中DOM更新的机制

由模型更改触发的DOM更新是所有现代前端框架的关键功能,Angular也不例外。 我们只需要这样指定表达式:

<span>Hello {{name}}</span>

或像这样的绑定:

<span [textContent]="'Hello ' + name"></span>

只要name属性更改,Angular就会神奇地更新DOM。 在外部看起来很容易,但是在内部实际上是一个非常复杂的过程。 DOM更新是Angular 变更检测机制的一部分,该机制主要包括三个主要操作:

  • DOM更新
  • 子组件输入绑定更新
  • 查询列表更新

本文探讨了变更检测的呈现部分如何工作。 如果您想知道如何做到这一点,请继续阅读并获得启示。

当引用源时,我假设该应用程序正在生产模式下运行。 开始吧。

组件的内部表示

在我们开始探索角度DOM更新功能之前,我们需要首先了解Angular如何表示引擎盖下的组件。 让我们简要地回顾一下。

视图

您可能从我的其他文章中了解到,应用程序中使用的每个组件Angular编译器都会生成一个工厂 。 例如,当Angular从工厂创建组件时,如下所示:

const factory = r.resolveComponentFactory(AComponent); factory.create(injector);

Angular使用此工厂实例化视图定义 ,该视图定义又用于创建组件View引擎盖下的Angular将应用程序表示为视图树 。 每个组件类型只有一个视图定义实例,该实例充当所有视图的模板。 但是Angular为每个组件实例创建一个单独的视图。

组件工厂主要由编译器通过模板解析生成的视图节点组成。 假设您像这样定义组件的模板:

<span>I am {{name}}</span>

编译器使用这些数据生成以下组件工厂:

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

它描述了组件视图的结构,并在实例化组件时使用。 jit_viewDef1是对创建视图定义的viewDef函数的引用。

视图定义将视图定义节点作为参数接收,这些参数类似于html的结构,但还包含许多特定于角度的细节。 在上面的示例中,第一个节点jit_elementDef2是元素定义,第二个jit_textDef3是文本定义。 Angular编译器生成许多不同的节点定义,但是出于本文的目的,我们仅对以上两个感兴趣。 让我们简要地回顾一下它们。

元素定义

元素定义是Angular为每个html元素生成的节点。 由于组件也是有效的自定义html元素 ,因此也会为自定义组件生成此类元素 。 元素节点可以包含其他元素节点和文本定义节点作为子元素,这反映在childCount属性中。

所有元素定义都是由elementDef函数生成的,因此工厂中使用的jit_elementDef2引用了该函数。 元素定义采用一些通用参数:

+------------------+-----------------------------------+ | Name | Description | +------------------+-----------------------------------+ | childCount | specifies how many children | | | the current element have | | namespaceAndName | the name of the html element | | fixedAttrs | attributes defined on the element | +------------------+-----------------------------------+

以及特定于特定Angular功能的其他功能:

+----------------------+------------------------------------------+ | 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 | +----------------------+------------------------------------------+

出于本文的目的,我们仅对绑定参数感兴趣。

文字定义

文本定义是Angular编译器为每个文本节点生成的节点定义。 通常,这些是元素定义节点的子节点,在我们的示例中就是这种情况。 这是由textDef函数生成的非常简单的节点定义。 它接收常量形式的解析表达式作为第二个参数。 例如,以下文本:

<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[2][suffix]
绑定

Angular使用绑定来定义每个节点对组件类属性的依赖性。 在更改检测期间,每个绑定都定义用于反映DOM中组件更改的操作类型。 此类操作由绑定标志确定,并构成以下列表:

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

元素和文本定义根据编译器标识的绑定标志在内部创建这些绑定。 每种节点类型都有特定于绑定生成的不同逻辑。

更新渲染器

我们最感兴趣的是在编译器生成的工厂View_AComponent_0的末尾列出的函数:

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

此功能称为updateRenderer。 它带有两个参数_ck和v。_ck是check的缩写,并引用函数p rodCheckAndUpdate 。 另一个参数是带有节点的组件视图。 每当Angular对组件执行更改检测并且更改检测机制为该功能提供参数时,都会执行updateRenderer函数。

updateRenderer函数的主要任务是从组件实例检索绑定属性的当前值,并调用_ck函数,以传递视图,节点索引和检索到的值。 需要了解的重要一点是,Angular分别为每个视图节点执行DOM更新-这就是为什么需要节点索引的原因。 在检查_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编译器生成的所有特定对象,我们就可以探索如何使用这些对象执行实际的DOM更新。

上面我们了解到,在更改检测期间,将updateRenderer函数传递给_ck函数,并且此参数引用p rodCheckAndUpdate 。 这是一个简短的通用函数,可进行大量调用,最终执行checkAndUpdateNodeInline函数。 在表达式计数超过10的情况下,该功能有所不同

checkAndUpdateNode函数只是一个路由器,用于区分以下类型的视图节点,并委托检查和更新各自的功能:

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

现在,让我们看看这些功能的作用。

类型元素

它使用功能CheckAndUpdateElement 。 该函数基本上检查绑定是否具有角度特殊形式[attr.name,class.name,style.some]或某些特定于节点的属性。

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

各个函数仅使用渲染器的相应方法在节点上执行所需的操作。

输入文字

两种版本都使用功能CheckAndUpdateText 。 这是功能的要点:

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

它基本上采用从updateRenderer函数传递的当前值,并将其与先前更改检测中的值进行比较。 视图在oldValues属性中保存旧值。 如果值更改,Angular将使用更新后的值来组成字符串,并使用渲染器更新DOM。

结论

我确信这是不可取的信息量。但是,通过了解这些信息,您在设计应用程序或调试DOM更新相关问题时会更好。 我建议您也使用调试器并遵循本文中介绍的执行逻辑。

您发现文章中的信息有帮助吗?

From: https://hackernoon.com/the-mechanics-of-dom-updates-in-angular-3b2970d5c03d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值