[译] Angular 属性绑定更新机制
原文链接: The mechanics of property bindings update in Angular
所有现代前端框架都是用组件来合成 UI,这样很自然就会产生父子组件层级,这就需要框架提供父子组件通信的机制。同样,Angular 也提供了两种方式来实现父子组件通信:输入输出绑定和共享服务。对于 stateless presentational components 我更喜欢输入输出绑定方式,然而对于 stateful container components 我使用共享服务方式。 本文主要介绍输入输出绑定方式,特别是当父组件输入绑定值变化时,Angular 如何更新子组件输入值。如果想了解 Angular 如何更新当前组件 DOM,可以查看 译 Angular DOM 更新机制,这篇文章也会有助于加深对本文的理解。由于我们将探索 Angular 如何更新 DOM 元素和组件的输入绑定属性,所以假定你知道 Angular 内部是如何表现组件和指令的,如果你不是很了解并且很感兴趣,可以查看 译 为何 Angular 内部没有发现组件, 这篇文章主要讲了 Angular 内部如何使用指令形式来表示组件。而本文对于组件和指令两个概念互换使用,因为 Angular 内部就是把组件当做指令。 模板绑定语法你可能知道 Angular 提供了 属性绑定语法 —— 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 元素做些额外的工作,但是对于子组件 @Component({ selector: 'b-comp',template: 'Comes from parent: {{textContent}}' }) export class BComponent { @Input() textContent; } 这样当父组件 你可能好奇 Angular 是怎么知道 Can’t bind to ‘textContent’ since it isn’t a known property of… 这些知识都很好理解,现在让我们进一步看看其内部发生了什么。 组件工厂尽管在子组件 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,currVal_0,currVal_1); },_v) { var _co = _v.component; var currVal_2 = _co.AText; _ck(_v,2,currVal_2); }); } 如果你读了 译 Angular DOM 更新机制 或 译 为何 Angular 内部没有发现组件,就会对上面代码中的各个视图节点比较熟悉了。前两个节点中, 节点绑定相同类型的节点使用相同的节点定义函数,但区别是接收的参数不同,比如 jit_directiveDef_5(...,{ textContent: [0,'textContent'] }, 其中,参数 directiveDef(...,props?: {[name: string]: [number,string]},...) props 参数是一个对象,每一个键为绑定属性名,对应的值为绑定索引和绑定属性名组成的数组,比如本例中只有一个绑定,textContent 对应的值为: {textContent: [0,'textContent']} 如果指令有多个绑定,比如: <b-comp [textContent]="AText" [otherProp]="AProp"> props 参数值也包含两个属性: jit_directiveDef5(49152,null,'textContent'],otherProp: [1,'otherProp'] },null), Angular 会使用这些值来生成当前指令节点的 binding,从而生成当前视图的指令节点。在变更检测时,每一个 binding 决定 Angular 使用哪种操作来更新节点和提供上下文信息,绑定类型是通过 BindingFlags 设置的(注:每一个绑定定义是 BindingDef,它的属性 flags: BindingFlags 决定 Angular 该采取什么操作,比如 export const enum BindingFlags { TypeProperty = 1 << 3, 注:上文说完了指令定义函数的参数,下面说说元素定义函数的参数。 本例中,因为 jit_elementDef2(...,...) 不同于指令节点,对元素节点来说,绑定参数结构是个二维数组,因为 export const enum BindingFlags { TypeProperty = 1 << 3,// 8 其他类型标志位已经在文章 译 Angular DOM 更新机制 有所解释: TypeElementAttribute = 1 << 0,TypeElementClass = 1 << 1,TypeElementStyle = 1 << 2, 编译器不会为指令定义提供绑定标志位,因为指令的绑定类型也只能是
注:
节点绑定 这一节主要讲的是对于元素节点来说,每一个节点的
binding 类型是由
BindingFlags 决定的;对于指令节点来说,每一个节点的
binding 类型只能是
updateRenderer 和 updateDirectives组件工厂代码里,编译器还为我们生成了两个函数: function (_ck,_v) { var _co = _v.component; var currVal_0 = _co.AText; var currVal_1 = _co.AProp; _ck(_v,currVal_1); },_v) { var _co = _v.component; var currVal_2 = _co.AText; _ck(_v,currVal_2); } 如果你读了 译 Angular DOM 更新机制,应该对第二个函数即 updateRenderer 有所熟悉。第一个函数叫做 updateDirectives。这两个函数都是 ViewUpdateFn 类型接口,两者都是视图定义的属性: interface ViewDefinition { flags: ViewFlags; updateDirectives: ViewUpdateFn; updateRenderer: ViewUpdateFn; 有趣的是这两个函数的函数体基本相同,参数都是 因为在变更检测期间,这是不同阶段的两个不同行为:
这两个操作是在变更检测的不同阶段执行,所以 Angular 需要两个独立的函数分别在对应的阶段调用:
这两个函数都会在 Angular 每次的变更检测时 被调用,并且函数参数也是在这时被传入的。让我们看看函数内部做了哪些工作。
<b-comp [textContent]="AText"></b-comp> <b-comp [textContent]="AText"></b-comp> <span [textContent]="AText"></span> <span [textContent]="AText"></span> 编译器生成的 function(_ck,_v) { var _co = _v.component; var currVal_0 = _co.AText; // update first component _ck(_v,currVal_0); var currVal_1 = _co.AText; // update second component _ck(_v,3,function(_ck,_v) { var _co = _v.component; var currVal_2 = _co.AText; // update first span _ck(_v,4,currVal_2); var currVal_3 = _co.AText; // update second span _ck(_v,5,currVal_3); } 没有什么更复杂的东西,这两个函数还不是重点,重点是 更新元素的属性从上文我们知道,编译器生成的 case BindingFlags.TypeElementAttribute -> setElementAttribute case BindingFlags.TypeElementClass -> setElementClass case BindingFlags.TypeElementStyle -> setElementStyle case BindingFlags.TypeProperty -> setElementProperty; 上面代码就是刚刚说的几个绑定类型,当绑定标志位是 注: 更新指令的属性上文中已经描述了
当然,只有在生命周期钩子在组件内定义了才被调用,Angular 使用 NodeDef 节点标志位来判断是否有生命周期钩子,如果查看源码你会发现类似如下代码(注:查看 L203-L207): if (... && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); } 和更新元素节点一样,更新指令时也同样把上一次的值存储在视图数据的属性 oldValues 里(注:即上面的 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- scala – 从辅助构造函数初始化val
- typescript – 在Angular2(TS)中导入模块的选项
- 带有ng-repeat函数的AngularJS InfDig错误(无限循
- Axis2发布的WebService中排除不需要公开的public
- 02 bootstrap "modal : is not a function&#
- angularjs – 在ng-repeat表中单击.量角器E2E测试
- yum CentOS系统包管理器
- angular – 循环依赖性错误TransferHttpCacheMod
- jax-ws之webservice security(安全)1
- 某课网—双剑合璧laravel AngularJS全栈开发知乎