一“括”抵千言 —— Angular 2中的绑定

Posted Angular中文社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一“括”抵千言 —— Angular 2中的绑定相关的知识,希望对你有一定的参考价值。

指令去哪儿了?

在Angular 1中,有七十多个内建指令,而到Angular 2中只剩下了二十来个。剩下的那五十多个指令去哪儿了?原来的那些功能还有办法实现吗?简单的回答是:已退役,因为用不着了。Angular 2中使用属性绑定和事件绑定代替了这五十多个消失的指令,还附送了那些以前不得不写自定义指令才能实现的那些功能,换句话说,它还替换了很多已知或未知的自定义指令。

Angular 2大幅度增强了Angular 1中原有的数据绑定功能 —— 它可以绑定到任意元素的任意属性(Property)。

此属性非彼属性

这里我为什么要标注Property原文呢?因为这里有一个很坑爹的事情:属性的中英文并没有一一对应的关系。具体来说,由于历史原因,有两个英文词汇的标准翻译都是“属性”,一个是Attribute,一个是Property。那么,它们有什么区别呢?至少在Angular 2看来,差别太大了。

Attribute是html中的概念,是指一个元素标签上的某些特征,如<input type="text" />中的type;Property是DOM中的概念,是指一个元素上的某些性质,如HTMLInputElement对象上的typevalue。即使在中文中,这两者也只有微妙的区别。接下来请听我细说从头:

Attribute的词根是tribut,其基本含义是“给予”,这个词可以理解为诞生时就被赋予的特征,因此是相对稳定的,具有静态性。而HTML本质上也是静态的,它是元素“出生”的地方,HTML中的元素标签就是规定如何造出这个元素的图纸。因此这个图纸上的选项,叫做Attribute。

Property的词根是proper,其基本含义是“拥有”,这个词可以理解为它当前的状态,因此是随时可能改变的,具有动态性。而DOM中的一切都是已经“出生”的元素,它们虽然是根据“元素标签”这个图纸造的,但是跟图纸已经不再有任何关联了。这些Property中记录的永远是元素“当前”的状态。因此,在DOM中,应该访问Property。

此外,Property和Attribute在具体的语义上也并不是一一对应的。
比如<input type="checkbox" checked />上的checked:当它作为Attribute使用时,无论你对它怎么赋值,它都是选中状态,这是因为它的语义是只要“出现”就表示选中,而无论取值是什么;但当它作为Property使用时,我们可以对它赋值为true或false,它们会正确的选中/取消选中这个检查框,如<input type="checkbox" [checked]="true" />。另外还有一些Property没有对应的Attribute,比如检查框用indeterminate这个Property来表示未定状态,却没有一个对应的Attribute。当然,也有一些Attribute没有对应的Property,比如表格中的colspan/rowspan,就只能通过Attribute来指定。但总体来说,即使仅仅考虑便利性,也值得优先使用Property。

不过,由于在HTML和DOM发展的早期阶段没有对这两者的语义进行过清晰的界定,于是在使用上就出现了混淆。比如:jQuery可以使用attr函数来修改一个元素的Attribute,严格来说这是不对的,后来它又引入了prop函数,但已经积重难返了;同样的,在Angular 1中也没有对此进行严格的区分(其实自身源码中区分了,但是文档中没有区分,也从未强调这一点,第三方代码中更是怎么写的都有)。

而Angular 2无论在代码中还是文档中都把它们明确区分开了。事实上,在Angular 2中很少会操纵Attribute —— 正如Attribute的语义所要求的。你看到的大多数绑定表达式,都是在操纵元素的Property。后面的文章中,如果没有特别注明,属性这个中文词所指的都是Property。

欢迎来到绑定的世界

在Angular 2中,可谓“一‘括’抵千言”。用括号代替了很多已知和未知的指令,甚至可以支持将来DOM标准扩展的新属性、新事件。在Angular 2中,分为三种绑定:属性绑定、事件绑定、双向绑定。其中,双向绑定其实是属性绑定和事件绑定组合成的语法糖,后面我们有专门一节来讲它。

我们先来看属性绑定。

属性绑定使用方括号,这很容易理解:我们以前在js中本来就可以用方括号来访问对象的属性嘛。比如说<input type="checkbox" [checked]="someValue" />,它的语义就是把这个input元素的checked属性绑定到模板表达式someValue,当someValue变化时,input元素的checked属性也会随之变化。而Angular 1中曾为此写过一个专门的指令ngChecked。那么对indeterminate的绑定呢?Angular 2中只要写<input type="checkbox" [indeterminate]="someValue" />就行,但在Angular 1中就得写一个自定义指令了。

我们再来看事件绑定。

事件绑定使用圆括号,这也很容易理解:我们的事件就是一个个js函数嘛,函数当然得用圆括号了。比如<button type="button" (click)="doSomeThing(someValue)">Click me!</button>,它的语义就是当click事件触发时,调用模板语句doSomeThing()。而在Angular 1中,几乎所有的事件类指令都是用来监听某个特定事件的,它的内建指令只支持了一小部分事件,而要想支持更多事件(比如touch系列)就必须写自定义指令了,Angular 2则不受此限制。

总之,“一‘括’抵千言”。Angular 2提供了更高级的绑定机制,就让Angular 1中的大量内建指令以及自定义指令直接退役了。

单向绑定?双向绑定?

无论是属性绑定还是事件绑定,都是单向绑定,因为其数据流是单向的:属性绑定是让数据从组件类流向模板,而事件绑定是让数据从模板流向组件类。双向绑定呢?顾名思义当然是数据既能从组件类流向模板,又能从模板流向组件类了。所以,为什么不把它们组合起来呢?没错!Angular 2中事实上并没有用来实现双向绑定的代码,双向绑定只是个语法糖而已。比如<input type="text" [(ngModel)]="name" />,其实拆解开之后是这样的:<input type="text" [ngModel]="name" (ngModelChange)="name = $event" />

这种语法糖给了我们很大的灵活性。开始的时候,我们可以使用普通的双向绑定形式。一旦需要在值发生变化时做某些处理,就可以把它拆解成原始形式,并扩展事件绑定的语句。而在Angular 1的时代,则需要用到ng-change指令甚至$watch。这种语法糖也让我们可以自由定义自己的支持双向绑定的属性,而不像Angular 1中那样只能用ng-model

当然,无论是否拆解开,它都是双向绑定,而双向绑定存在一些固有的缺点。

其中比较明显的就是会影响变更检测。不过在Angular 2中,由于变更检测速度提高了500%,所以基本上不用再担心此问题。

另一个深层但更难解决的问题在于它会导致双向数据流,让对数据的改动过程难以跟踪,这通常发生在较大的应用系统中。那么,Angular 2是如何解决这个问题的?

  • 首先,它对RxJS做了很好的集成。这并不意味着它必须依赖RxJS,而是你可以在较大的系统中把数据流完全交给RxJS来管理,而数据流的处理,正是RxJS的强项。所以,即使在小系统中你并不需要深度使用RxJS,但是仍然值得学一学Angular 2和RxJS的集成。

  • 其次,它提倡组件化编程。一个基于组件封装出来的体系,可以对数据流进行较好的封装和转换,避免出现跨越多个层次的数据流。

  • 再次,它提供了组件生命周期钩子机制。可以让你切入组件生命周期(包括变更检测)的各个阶段,提高了数据流的可视性。

  • 最后,它提供了层次化依赖注入体系。可以轻松创建某组件及其子树范围内专用的服务,限制数据流的影响范围,也有利于对数据流进行理解和跟踪。

Angular本身无法解决这个问题,它只是给你提供了解决此问题的工具和实践。但是如果在架构设计时不认真考虑此问题,仍将导致难以维护的代码。说到底,代码质量是“人”的问题,有人能用Angular 1写出清晰的数据流,也有人能把RxJS的程序写成一团乱麻。

更高级的绑定语法

Angular 2中的属性绑定和事件绑定,不但已经覆盖了Angular 1中的大部分指令,而且引入了一些小而酷的特性,这一节我就带大家简略的看一下:

  • 只在按回车时触发事件:<input type="text" (keyup.enter)="doSomething()"/>

    在keyup事件中,可以用一个小数点后跟键名进行过滤,这段代码中,只有当用户松开回车键的时候才会调用doSomething()方法。而传统上,你需要写一些代码才能完成此项工作。

  • 控制CSS类的显示:<button type="button" [class.active]="isActive">Click Me!</button>

    class是属性绑定中一种特殊的绑定语法,它会根据isActive表达式的值来决定是否为当前元素添加class.后面所指定的CSS类名。

  • 控制CSS样式(基本):<span [style.color]="someColor">Example</span>

    style是属性绑定中一种特殊的绑定语法,它会把style.后面跟的样式设置为someColor的值。

  • 控制CSS样式(带单位):<div [style.height.em]="someHeight">Example</div>

    对于一些带单位的CSS值,比如height等,可以直接把单位跟在名称后面,而值中只需要指定一个数值就行了。你不但可以指定em为单位,还可以指定%等各种长度单位,比如<div [style.width.%]="10">Example</div> —— 虽然%并不是合法的js标识符。

  • 为残障人士提供支持:<button [attr.aria-label]="help">Help</button>

    为这个按钮加上ARIA标签,当残障人士使用屏幕阅读器查看此网页时,它就会发现并阅读这个标签,这个标签的内容是可以由程序控制的。

当然,这还只是Angular 2数据绑定机制的冰山一角,结合“模板引用变量”等特性可以写出更酷的代码。不过“模板”又是另一个很大的话题了,我会在另外的文章中讲解。

组件与绑定

仅对原生DOM元素进行绑定显然是不够的,真正让数据绑定机制发挥巨大能量的地方还是在它与自定义组件的结合。从自定义组件中为自己的模板提供数据,并不需要做什么特别的工作,直接定义成普通的类属性就够了,对数据类型也没有任何限制,比如可以使用RxJS所提供的Observable类型。

但是如果组件需要和外界通讯,那么它通常还需要开放一些输入参数,让外界可以把参数传给它,并且它要能感知到数据源的变化。除此之外,它还可能发出一些事件,通知外界发生了什么,以便对方做相应的处理。

前者,其实就是属性绑定,在组件看来,它是“在输入数据”,因此需要为该属性添加@Input()装饰器。后者,其实就是事件绑定,在组件看来,它是“在输出数据”,因此需要为该属性添加@Output()装饰器。

通过这些输入输出属性,组件对自己的内部实现进行了封装,对外界只开放有限的接口,优化了程序的结构。当然,就像数据流中的情况一样,Angular只是提供了工具,要写出具有良好可维护性的代码,还需要对程序进行良好的逻辑分层,并逐层进行适当的信息隐蔽。

可见,Angular 2中的新型数据绑定机制不但本身强大到可以直接代替很多指令,而且为开发自定义组件提供了强力的基础设施。数据绑定是Angular 2入门阶段最重要的一项知识。

以上是关于一“括”抵千言 —— Angular 2中的绑定的主要内容,如果未能解决你的问题,请参考以下文章

2.3.5Google Analytics高级应用——热力图

职责链设计模式最简单的实例

使用 Emacs 创建 OAuth 2.0 的 UML 序列图 | Linux 中国

绑定中的 Angular 2 绑定。事件内插值

Angular 2 和 Angularfire2 中的三向绑定

微信表情150个限制怎么破?教你一招