ホームページ > ウェブフロントエンド > jsチュートリアル > [翻訳] Angular プロパティ バインディング更新メカニズム - Laravel/Angular テクノロジー共有

[翻訳] Angular プロパティ バインディング更新メカニズム - Laravel/Angular テクノロジー共有

寻∝梦
リリース: 2018-09-07 16:11:11
オリジナル
1353 人が閲覧しました
この記事では、主に angularjs 属性のバインディングと更新メカニズム、および angularjs 更新属性の詳細な説明について説明します。それでは、この記事を一緒に読んでみましょう

angularjs 属性のバインディング更新メカニズムの説明:

すべての最新のフロントエンド フレームワークは、コンポーネントを使用して UI を合成します。その結果、必然的に親子コンポーネント階層が形成され、親子コンポーネント通信のメカニズムを提供するフレームワークが必要になります。同様に、Angular では、親子コンポーネント通信を実装する 2 つの方法、入出力バインディング共有サービス も提供しています。 ステートレス プレゼンテーション コンポーネント の場合は入出力バインディング アプローチを好みますが、ステートフル コンテナ コンポーネント の場合は共有サービス アプローチを使用します。

この記事では、主に入力および出力バインディング メソッド、特に親コンポーネントの入力バインディング値が変更されたときに Angular が子コンポーネントの入力値を更新する方法を紹介します。 Angular が現在のコンポーネント DOM を更新する方法を知りたい場合は、翻訳された Angular DOM 更新メカニズム を参照してください。この記事の理解を深めることもできます。 Angular が DOM 要素とコンポーネントの入力バインディング プロパティを更新する方法について説明するので、コンポーネントとディレクティブが Angular で内部的にどのように表現されるかを理解していることを前提としています。詳しくはなくても興味がある場合は、理解することができます。 「Angular が内部でコンポーネントを見つけられない理由」の翻訳 をチェックしてください。この記事は主に、Angular がディレクティブ形式を使用してコンポーネントを表す方法について説明しています。 Angular は内部的にコンポーネントを命令として扱うため、この記事ではコンポーネントと命令という 2 つの概念を同じ意味で使用します。

テンプレート バインディング構文

Angular が プロパティ バインディング構文 - [] を提供していることはご存知かもしれません。この構文は非常に一般的で、子コンポーネントまたは DOM 要素でネイティブに使用できます。親コンポーネントから子コンポーネント b-comp またはネイティブ DOM 要素 span にデータを渡したい場合は、親コンポーネント テンプレートにこれを記述できます: [],这个语法很通用,它可以用在子组件上,也可以用在原生 DOM 元素上。如果你想从父组件把数据传给子组件 b-comp 或者原生 DOM 元素 span,你可以在父组件模板中这么写:

import { Component } from '@angular/core';

@Component({
  moduleId: module.id,
  selector: 'a-comp',
  template: `
      <b-comp [textContent]="AText"></b-comp>
      <span [textContent]="AText"></span>
  `
})
export class AComponent {
  AText = 'some';
}
ログイン後にコピー

你不必为原生 DOM 元素做些额外的工作,但是对于子组件 b-comp 你需要申明输入属性 textContent

@Component({
    selector: 'b-comp',
    template: 'Comes from parent: {{textContent}}'
})
export class BComponent {
    @Input() textContent;
}
ログイン後にコピー

这样当父组件 AComponent.AText 属性改变时,Angular 会自动更新子组件 BComponent.textContent 属性,和原生元素 span.textContent 属性。同时,还会调用子组件 BComponent 的生命周期钩子函数 ngOnChanges(注:实际上还有 ngDoCheck,见下文)。

你可能好奇 Angular 是怎么知道 BComponentspan 支持 textContent 绑定的。这是因为 Angular 编译器在解析模板时,如果遇到简单 DOM 元素如 span,就去查找这个元素是否定义在 dom_element_schema_registry,从而知道它是 HTMLElement 子类,textContent 是其中的一个属性(注:可以试试如果 span 绑定一个 [abc]=AText 就报错,没法识别 abc 属性);如果遇到了组件或指令,就去查看其装饰器 @Component/@Directive 的元数据 input 属性里是否有该绑定属性项,如果没有,编译器同样会抛出错误:

Can’t bind to ‘textContent’ since it isn’t a known property of …
ログイン後にコピー

这些知识都很好理解,现在让我们进一步看看其内部发生了什么。

组件工厂

尽管在子组件 BComponentspan 元素绑定了输入属性,但是输入绑定更新所需要的信息全部在父组件 AComponent 的组件工厂里。让我们看下 AComponent 的组件工厂代码:

function View_AComponent_0(_l) {
  return jit_viewDef1(0, [
     jit_elementDef_2(..., 'b-comp', ...),
     jit_directiveDef_5(..., jit_BComponent6, [], {
         textContent: [0, 'textContent']
     }, ...),
     jit_elementDef_2(..., 'span', [], [[8, 'textContent', 0]], ...)
  ], function (_ck, _v) {
     var _co = _v.component;
     var currVal_0 = _co.AText;
     var currVal_1 = 'd';
     _ck(_v, 1, 0, currVal_0, currVal_1);
  }, function (_ck, _v) {
     var _co = _v.component;
     var currVal_2 = _co.AText;
     _ck(_v, 2, 0, currVal_2);
  });
}
ログイン後にコピー

如果你读了 译 Angular DOM 更新机制译 为何 Angular 内部没有发现组件,就会对上面代码中的各个视图节点比较熟悉了。前两个节点中,jit_elementDef_2 是元素节点,jit_directiveDef_5 是指令节点,这两个组成了子组件 BComponent;第三个节点 jit_elementDef_2 也是元素节点,组成了 span 元素。(想看更多就到PHP中文网AngularJS开发手册中学习)

节点绑定

相同类型的节点使用相同的节点定义函数,但区别是接收的参数不同,比如 jit_directiveDef_5 节点定义函数参数如下:

jit_directiveDef_5(..., jit_BComponent6, [], {
    textContent: [0, 'textContent']
}, ...),
ログイン後にコピー

其中,参数 {textContent: [0, 'textContent']}

directiveDef(..., props?: {[name: string]: [number, string]}, ...)
ログイン後にコピー
ネイティブ DOM 要素に対して追加の作業を行う必要はありませんが、子コンポーネント b-comp に対しては、入力属性 textContent を宣言する必要があります:
{textContent: [0, 'textContent']}
ログイン後にコピー
This親コンポーネントの AComponent。AText プロパティが変更されると、Angular は子コンポーネントの BComponent.textContent プロパティとネイティブ要素の span.textContent プロパティ。同時に、サブコンポーネント <code>BComponent のライフサイクル フック関数 ngOnChanges も呼び出されます (注: 実際には ngDoCheck があります。以下を参照してください)。 BComponentspantextContent バインディングをサポートしていることを Angular がどのように認識するのか興味があるかもしれません。これは、Angular コンパイラーがテンプレートを解析するときに、span などの単純な DOM 要素が見つかった場合、その要素が

dom_element_schema_registry で定義されているかどうかをチェックし、それによってそれが HTMLElement サブクラスであることを認識するためです。 、 textContent は属性の 1 つです (注: span[abc]=AText にバインドされている場合は、エラーが発生します。コンポーネントまたはディレクティブが見つかった場合は、そのデコレータ @Component/@Directive のメタデータ <code>input 属性が存在するかどうかを確認してください。 にはバインディング属性項目がありますが、そうでない場合、コンパイラーはエラーもスローします:

<b-comp [textContent]="AText" [otherProp]="AProp">
ログイン後にコピー
ログイン後にコピー
この知識は簡単に理解できます。次に、内部で何が起こっているかを詳しく見てみましょう。 コンポーネント ファクトリ

入力属性は子コンポーネント BComponent 要素と span 要素にバインドされていますが、入力バインディングの更新に必要な情報はすべて親コンポーネント にあります。 AComponent のコンポーネント ファクトリ内。 AComponent のコンポーネント ファクトリ コードを見てみましょう: 🎜
jit_directiveDef5(49152, null, 0, jit_BComponent6, [], {
    textContent: [0, 'textContent'],
    otherProp: [1, 'otherProp']
}, null),
ログイン後にコピー
ログイン後にコピー
🎜 🎜Translation Angular DOM updateメカニズム🎜 または 🎜Translation WhyComponents are not found inside Angular🎜 を読むと、それぞれのことを理解できるでしょう。上記のコードの View ノードの方がよく知られています。最初の 2 つのノードのうち、jit_elementDef_2 は要素ノード、jit_directiveDef_5 はサブコンポーネント BComponent を構成します。 3 番目のノード jit_elementDef_2 も要素ノードであり、span 要素を形成します。 (さらに詳しく知りたい場合は、PHP 中国語 Web サイトAngularJS 開発マニュアル🎜 にアクセスして学習してください)🎜

ノードバインディング

🎜同じタイプのノードは同じノード定義関数を使用しますが、異なる点は、jit_directiveDef_5 など、受信するパラメータが異なることです。ノード定義関数のパラメータは次のとおりです。 🎜
export const enum BindingFlags {
    TypeProperty = 1 << 3,
ログイン後にコピー
ログイン後にコピー
🎜 このうち、パラメータ {textContent: [0, 'textContent']} は 🎜props🎜 と呼ばれます。 🎜directiveDef🎜 関数のパラメータリストを表示できます: 🎜
jit_elementDef2(..., 'span', [], [[8, 'textContent', 0]], ...)
ログイン後にコピー
ログイン後にコピー
🎜 🎜props🎜 パラメーターはオブジェクトであり、各キーはバインディング属性名であり、値はバインディング インデックスとバインディング属性名で構成される配列です。たとえば、この例では、バインディングは 🎜textContent🎜 1 つだけです。対応する値は次のとおりです: 🎜
export const enum BindingFlags {
    TypeProperty = 1 << 3, // 8
ログイン後にコピー
ログイン後にコピー
🎜 命令に次のような複数のバインディングがある場合: 🎜
<b-comp [textContent]="AText" [otherProp]="AProp">
ログイン後にコピー
ログイン後にコピー

props 参数值也包含两个属性:

jit_directiveDef5(49152, null, 0, jit_BComponent6, [], {
    textContent: [0, 'textContent'],
    otherProp: [1, 'otherProp']
}, null),
ログイン後にコピー
ログイン後にコピー

Angular 会使用这些值来生成当前指令节点的 binding,从而生成当前视图的指令节点。在变更检测时,每一个 binding 决定 Angular 使用哪种操作来更新节点和提供上下文信息,绑定类型是通过 BindingFlags 设置的(注:每一个绑定定义是 BindingDef,它的属性 flags: BindingFlags 决定 Angular 该采取什么操作,比如 Class 型绑定和 Style 型绑定都会调用对应的操作函数,见下文)。比如,如果是属性绑定,编译器会设置绑定标志位为:

export const enum BindingFlags {
    TypeProperty = 1 << 3,
ログイン後にコピー
ログイン後にコピー
注:上文说完了指令定义函数的参数,下面说说元素定义函数的参数。

本例中,因为 span 元素有属性绑定,编译器会设置绑定参数为 [[8, 'textContent', 0]]

jit_elementDef2(..., 'span', [], [[8, 'textContent', 0]], ...)
ログイン後にコピー
ログイン後にコピー

不同于指令节点,对元素节点来说,绑定参数结构是个二维数组,因为 span 元素只有一个绑定,所以它仅仅只有一个子数组。数组 [8, 'textContent', 0] 中第一个参数也同样是绑定标志位 BindingFlags,决定 Angular 应该采取什么类型操作(注:[8, 'textContent', 0] 中的 8 表示为 property 型绑定):

export const enum BindingFlags {
    TypeProperty = 1 << 3, // 8
ログイン後にコピー
ログイン後にコピー

其他类型标志位已经在文章 译 Angular DOM 更新机制 有所解释:

TypeElementAttribute = 1 << 0,
TypeElementClass = 1 << 1,
TypeElementStyle = 1 << 2,
ログイン後にコピー

编译器不会为指令定义提供绑定标志位,因为指令的绑定类型也只能是 BindingFlags.TypeProperty

注:节点绑定 这一节主要讲的是对于元素节点来说,每一个节点的 binding 类型是由 BindingFlags 决定的;对于指令节点来说,每一个节点的 binding 类型只能是 BindingFlags.TypeProperty

updateRenderer 和 updateDirectives

组件工厂代码里,编译器还为我们生成了两个函数:

function (_ck, _v) {
    var _co = _v.component;
    var currVal_0 = _co.AText;
    var currVal_1 = _co.AProp;
    _ck(_v, 1, 0, currVal_0, currVal_1);
},
function (_ck, _v) {
    var _co = _v.component;
    var currVal_2 = _co.AText;
    _ck(_v, 2, 0, currVal_2);
}
ログイン後にコピー

如果你读了 译 Angular DOM 更新机制,应该对第二个函数即 updateRenderer 有所熟悉。第一个函数叫做 updateDirectives。这两个函数都是 ViewUpdateFn 类型接口,两者都是视图定义的属性:

interface ViewDefinition {
  flags: ViewFlags;
  updateDirectives: ViewUpdateFn;
  updateRenderer: ViewUpdateFn;
ログイン後にコピー

有趣的是这两个函数的函数体基本相同,参数都是 _ck_v,并且两个函数的对应参数都指向同一个对象,所以为何需要两个函数?

因为在变更检测期间,这是不同阶段的两个不同行为:

  • 更新子组件的输入绑定属性

  • 更新当前组件的 DOM 元素

这两个操作是在变更检测的不同阶段执行,所以 Angular 需要两个独立的函数分别在对应的阶段调用:

  • updateDirectives——变更检测的开始阶段被调用,来更新子组件的输入绑定属性

  • updateRenderer——变更检测的中间阶段被调用,来更新当前组件的 DOM 元素

这两个函数都会在 Angular 每次的变更检测时 被调用,并且函数参数也是在这时被传入的。让我们看看函数内部做了哪些工作。

_ck 就是 check 的缩写,其实就是函数 prodCheckAndUpdateNode,另一个参数就是 组件视图数据。函数的主要功能就是从组件对象里拿到绑定属性的当前值,然后和视图数据对象、视图节点索引等一起传入 prodCheckAndUpdateNode 函数。其中,因为 Angular 会更新每一个视图的 DOM,所以需要传入当前视图的索引。如果我们有两个 span 和两个组件:

<b-comp [textContent]="AText"></b-comp>
<b-comp [textContent]="AText"></b-comp>
<span [textContent]="AText"></span>
<span [textContent]="AText"></span>
ログイン後にコピー

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

function(_ck, _v) {
    var _co = _v.component;
    var currVal_0 = _co.AText;
    
    // update first component
    _ck(_v, 1, 0, currVal_0);
    var currVal_1 = _co.AText;
    
    // update second component
    _ck(_v, 3, 0, currVal_1);
}, 
function(_ck, _v) {
    var _co = _v.component;
    var currVal_2 = _co.AText;
    
    // update first span
    _ck(_v, 4, 0, currVal_2);
    var currVal_3 = _co.AText;

    // update second span
    _ck(_v, 5, 0, currVal_3);
}
ログイン後にコピー

没有什么更复杂的东西,这两个函数还不是重点,重点是 _ck 函数,接着往下看。

更新元素的属性

从上文我们知道,编译器生成的 updateRenderer 函数会在每一次变更检测被调用,用来更新 DOM 元素的属性,并且其参数 _ck 就是函数 prodCheckAndUpdateNode。对于 DOM 元素的更新,该函数经过一系列的函数调用后,最终会调用函数 checkAndUpdateElementValue,这个函数会检查绑定标志位是 [attr.name, class.name, style.some] 其中的哪一个,又或者是属性绑定:

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;
ログイン後にコピー

上面代码就是刚刚说的几个绑定类型,当绑定标志位是 BindingFlags.TypeProperty,会调用函数 setElementProperty,该函数内部也是通过调用 DOM Renderer 的 setProperty 方法来更新 DOM。

注:setElementProperty 函数里这行代码 view.renderer.setProperty(renderNode,name, renderValue);,renderer 就是 Renderer2 interface,它仅仅是一个接口,在浏览器平台下,它的实现就是 DefaultDomRenderer2

更新指令的属性

上文中已经描述了 updateRenderer 函数是用来更新元素的属性,而 updateDirective 是用来更新子组件的输入绑定属性,并且变更检测期间传入的参数 _ck 就是函数 prodCheckAndUpdateNode。只是进过一系列函数调用后,最终调用的函数却是checkAndUpdateDirectiveInline,这是因为这次节点的标志位是 NodeFlags.TypeDirective

checkAndUpdateDirectiveInline 函数主要功能如下:

  1. 从当前视图节点里获取组件/指令对象

  2. 检查组件/指令对象的绑定属性值是否发生改变

  3. 如果属性发生改变:

    a. 如果变更策略设置为 OnPush,设置视图状态为 checksEnabled

    b. 更新子组件的绑定属性值

    c. 准备 SimpleChange 数据和更新视图的 oldValues 属性,新值替换旧值

    d. 调用生命周期钩子 ngOnChanges

  4. 如果该视图是首次执行变更检测,则调用生命周期钩子 ngOnInit

  5. 调用生命周期钩子 ngDoCheck

当然,只有在生命周期钩子在组件内定义了才被调用,Angular 使用 NodeDef 节点标志位来判断是否有生命周期钩子,如果查看源码你会发现类似如下代码:

if (... && (def.flags & NodeFlags.OnInit)) {
  directive.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
  directive.ngDoCheck();
}
ログイン後にコピー

和更新元素节点一样,更新指令时也同样把上一次的值存储在视图数据的属性 oldValues 里(注:即上面的 3.c 步骤)。

好了,本篇文章到这就结束了(想看更多就到PHP中文网AngularJS使用手册中学习),有问题的可以在下方留言提问。


以上が[翻訳] Angular プロパティ バインディング更新メカニズム - Laravel/Angular テクノロジー共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート