本帖最后由 郝永亮 于 2018-1-25 16:41 编辑
这节讲angular的模板语法和内置指令,过滤器 0. Angular内置模板引擎,可以很方便的使用指令将数据显示到页面。下图是指令和数据渲染页面的过程
1. 模板是编写 Angular 组件最重要的一环,你至少需要深入理解以下知识点才能玩转 Angular 模板: Mustache(八字胡)语法
模板内的局部变量
属性绑定、事件绑定、双向绑定
在模板里面使用结构型指令 *ngIf、*ngFor、ngSwitch
在模板里面使用属性型指令 NgClass、NgStyle、NgModel
在模板里面使用管道格式化数据
1.1 对比各种 JS 模板引擎的设计思路
几乎每一款前端框架都会提供自己的模板语法,在 jQuery 如日中天的时代,有 Handlebars 那种功能超强的模板。最近有 React 推崇的 JSX 模板写法,当然还有 Angular 提供的那种与“指令”紧密结合的模板语法。
综合来说,无论是哪一种前端模板,大家都比较推崇“轻逻辑”( logic-less )的设计思路。
何为“轻逻辑”?
简而言之,所谓“轻逻辑”就是说,你不能在模板里面编写非常复杂的 JavaScript 表达式。比如,Angular 的模板语法就有规定:
~你不能在模板里面 new 对象
~不能使用=、+=、-=这类的表达式
~不能用++、--运算符
~不能使用位运算符
[JavaScript] 纯文本查看 复制代码 <ul>[/size]
[size=3] <li *ngFor="let race of races">[/size]
[size=3] {{race.name}}[/size]
[size=3] </li>[/size]
[size=3]</ul>
为什么要“轻逻辑”?
很明显,浏览器不认识 *ngFor 和 {{...}} 这种语法,所以必须在浏览器里面进行“编译”,获得对应的模板函数,然后再把数据传递给模板函数,最终结合起来获得一堆 HTML 标签,然后才能把这一堆标签插入到 DOM 树里面去。
如果启用了 AOT,处理的步骤有一些变化,@angular/cli 会对模板进行“静态编译”,避免在浏览器里面动态编译的过程。
而 Handlebars 这种模板引擎完全是运行时编译模板字符串的,你可以编写以下代码
[AppleScript] 纯文本查看 复制代码 //定义模板字符串[/size]
[size=3]var source=`[/size]
[size=3] <ul>[/size]
[size=3] {{#each races}}[/size]
[size=3] <li>{{name}}</li>[/size]
[size=3] {{/each}}[/size]
[size=3] </ul>[/size]
[size=3] `;[/size]
[size=3]//在运行时把模板字符串编译成JS函数[/size]
[size=3]var templateFn=Handlebars.compile(source);[/size]
[size=3]//把数据传给模板函数,获得最终的HTML[/size]
[size=3]var html=templateFn([[/size]
[size=3] {name:'人族'},[/size]
[size=3] {name:'神族'},[/size]
[size=3] {name:'虫族'}[/size]
[size=3]]);
注意到 Handlebars.compile 这个调用了吧?这个地方的本质是在运行时把模板字符串“编译”成了一个 JS 函数。
鉴于 JS 解释执行的特性,你可能会担忧这里会有性能问题。这种担忧是合理的,但是 Handlebars 是一款非常优秀的模板引擎,它在内部做了各种优化和缓存处理。模板字符串一般只会在第一次被调用的时候编译一次,Handlebars 会把编译好的函数缓存起来,后面再次调用的时候会从缓存里面获取,而不会多次进行“编译”。
上面我们多次提到了“编译”这个词,所以很显然这里有一个东西是无法避免的,那就是我们必须提供一个 JS 版的“编译器”,让这个“编译器”运行在浏览器里面,这样才能在运行时把用户编写的模板字符串“编译”成模板函数。
有一些模板引擎会真的去用 JS 编写一款“编译器”出来,比如 Angular 和 Handlebars,它们都真的编写了一款 JS( TS )版的编译器。而有一些简单的模板引擎只是用正则表达式做了字符串替换而已,显得特别简陋。这种简陋的模板引擎对模板的写法有非常多的限制,因为它不是真正的编译器,能支持的语法特性非常有限。
所以,评估一款模板引擎的强弱,最核心的东西就是评估它的“编译器”做得怎么样。但是不管怎么说,毕竟是 JS 版的“编译器”,我们不可能把它做得像 g++ 那么强大,也没有必要做得那么强大,因为这个 JS 版的编译器需要在浏览器里面运行,搞得太复杂浏览器拖不动!
以上就是为什么大多数模板引擎都要强调“轻逻辑”的最根本原因。
对于 Angular 来说,强调“轻逻辑”还有另一个原因:在组件的整个生命周期里面,模板函数会被执行很多次。你可以想象, Angular 每次要刷新组件的外观的时候,都需要去调用一下模板函数,如果你在模板里面编写了非常复杂的代码,一定会增加渲染时间,用户一定会感到界面有“卡顿”。
人眼的视觉延迟大约是100ms到400ms之间,如果整个页面的渲染时间超过400ms,界面基本上就卡得没法用了。有一些做游戏的开发者会追求60fps刷新率的细腻感觉,60分之1秒约等于16.7ms,如果 UI 整体的渲染时间超过了16.7ms,就没法达到这个要求了。
轻逻辑( logic-less )带来了效率的提升,也带来了一些不方便,比如很多模板引擎都实现了 if 语句,但是没有实现 else,所以开发者们在编写复杂业务逻辑的时候模板代码会显得非常啰嗦。
目前来说,并没有完美的方案能同时兼顾运行效率和语法表现能力,这里只能取一个平衡。
1.2 Mustache (八字胡)语法
Mustache 语法也就是你们说的双花括号语法{{...}},老外觉得它像八字胡子,很奇怪啊,难道老外喜欢侧着头看东西?
好消息是,很多模板引擎都接受了 Mustache 语法,这样一来学习量又降低了不少,开心吧?
关于 Mustache 语法,你需要掌握3点:
它可以获取到组件里面定义的属性值。
它可以自动计算简单的数学表达式,例如:加减乘除、取模。
它可以获得方法的返回值。
请依次看例子:
1.2.1插值语法关键代码实例:
[AppleScript] 纯文本查看 复制代码 private gameName:String = "王者荣耀"; [AppleScript] 纯文本查看 复制代码 <div>
{{gameName}}
</div>
1.2.2简单的数学表达式求值:
[AppleScript] 纯文本查看 复制代码 <h3>1+1={{1+1}}</h3>
1.2.3调用组件里面定义的方法:
[AppleScript] 纯文本查看 复制代码 <h3>可以调用方法{{getVal()}}</h3> [AppleScript] 纯文本查看 复制代码 public getVal():any{[/size]
[size=3] return 65535;[/size]
[size=3]}
1.2.4模板内的局部变量
[AppleScript] 纯文本查看 复制代码 <input type="text" #heroInput>[/size]
[size=3]<p>{{heroInput.value}}</p>[/size]
[size=3]<p>{{heroInput.text}}</p>
其中input标签中的heroInput是局部变量名,#开头,表示当前input对象
模板局部变量 > 指令中的同名变量 > 组件中的同名属性。
这种优先级规则和 JSP 里面的变量取值规则非常类似,对比一下很好理解对不对?你可以自己写代码测试一下。
1.3 属性绑定
属性绑定是用方括号来做的,写法:
[AppleScript] 纯文本查看 复制代码 <img [src]="imgSrc" />[/size]
[size=3]public imgSrc:string="./assets/imgs/1.jpg";
很明显,这种绑定是单向的。
1.4 事件绑定事件绑定是用圆括号来做的,写法:
[AppleScript] 纯文本查看 复制代码 <button class="btn btn-success" (click)="btnClick($event)">测试事件</button>
对应 Component 内部的方法定义:
[AppleScript] 纯文本查看 复制代码 public btnClick(event):void{[/size]
[size=3] alert("测试事件绑定!");[/size]
[size=3]}
1.5 双向绑定
双向绑定是通过方括号里面套一个圆括号来做的,模板写法:
[AppleScript] 纯文本查看 复制代码 <input type="text" [(ngModel)] = "dobuleBindName"/>[/size]
[size=3]{{dobuleBindName}}
对应组件内部的属性定义:
[AppleScript] 纯文本查看 复制代码 private dobuleBindName:String = "";
AngularJS 是第一个把“双向数据绑定”这个特性带到前端来的框架,这也是 AngularJS 当年最受开发者追捧的特性,之一。
根据 AngularJS 团队当年讲的故事,“双向数据绑定”这个特性可以大幅度压缩前端代码的规模。大家可以回想一下 jQuery 时代的做法,如果要实现类似的效果,是不是要自己去编写大量的代码?尤其是那种大规模的表单,一大堆的赋值和取值操作,都是非常丑陋的“面条”代码,而有了“双向数据绑定”特性之后,一个绑定表达式就搞定。
目前,主流的几款前端框架都已经接受了“双向数据绑定”这个特性。
当然,也有一些人不喜欢“双向数据绑定”,还有人专门写了文章来进行批判,也算是前端一景。
1.5 在模板里面使用结构型指令
Angular 有3个内置的结构型指令:*ngIf、*ngFor、ngSwitch。ngSwitch 的语法比较啰嗦,使用频率小一些。
*ngIf 代码实例:
[AppleScript] 纯文本查看 复制代码 <div *ngIf="showed" >显示/不显示</div>[/size]
[size=3]<button type="button" (click) = "toggleShow()">切换</button>
[AppleScript] 纯文本查看 复制代码 private showed:boolean = false;[/size]
[size=3]public toggleShow():void{[/size]
[size=3] this.showed = !this.showed;[/size]
[size=3]}
*ngFor代码示例
[AppleScript] 纯文本查看 复制代码 <div *ngFor="let fruit of fruits;let i = index; ">[/size]
[size=3] <div>{{i+1}}-{{fruit}}</div>[/size]
[size=3]</div>
[AppleScript] 纯文本查看 复制代码 private fruits:Array<any> = ['苹果', '香蕉', '樱桃'] ;
*ngSwitch 代码实例:
[AppleScript] 纯文本查看 复制代码 <div [ngSwitch]="mapStatus">[/size]
[size=3] <p *ngSwitchCase="0">下载中...</p>[/size]
[size=3] <p *ngSwitchCase="1">正在读取...</p>[/size]
[size=3] <p *ngSwitchDefault>系统繁忙...</p>[/size]
[size=3]</div> [AppleScript] 纯文本查看 复制代码 public mapStatus:number=1; 特别注意:一个 HTML 标签上只能同时使用一个结构型的指令。因为“结构型”指令会修改 DOM 结构,如果在一个标签上使用多个结构型指令,大家都一起去修改 DOM 结构,到时候到底谁说了算?
那么需要在同一个 HTML 上使用多个结构型指令应该怎么办呢?有两个办法:
加一层空的 div 标签
加一层<ng-container>
1.6 在模板里面使用属性型指令
使用频率比较高的3个内置指令是:NgClass、NgStyle、NgModel。
NgClass 使用案例代码:
[AppleScript] 纯文本查看 复制代码 <div [ngClass]="currentClasses">同时批量设置多个样式</div>[/size]
[size=3]<button class="btn btn-success" (click)="setCurrentClasses()">设置</button> [AppleScript] 纯文本查看 复制代码 public currentClasses: {};
public canSave: boolean = true;
public isUnchanged: boolean = true;
public isSpecial: boolean = true;
setCurrentClasses() {
this.currentClasses = {
'saveable': this.canSave,
'modified': this.isUnchanged,
'special': this.isSpecial
};
} [AppleScript] 纯文本查看 复制代码 .saveable{
font-size: 18px;
}
.modified {
font-weight: bold;
}
.special{
background-color: #ff3300;
}
NgStyle 使用案例代码:
[AppleScript] 纯文本查看 复制代码 <div [ngStyle]="currentStyles">
用NgStyle批量修改内联样式!
</div>
<button class="btn btn-success" (click)="setCurrentStyles()">设置</button>
[AppleScript] 纯文本查看 复制代码 public currentStyles: {}
public canSave:boolean=false;
public isUnchanged:boolean=false;
public isSpecial:boolean=false;
setCurrentStyles() {
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '36px' : '12px'
};
}
ngStyle 这种方式相当于在代码里面写 CSS 样式,比较丑陋,违反了注意点分离的原则,而且将来不太好修改,非常不建议这样写。
NgModel 使用案例代码:
[AppleScript] 纯文本查看 复制代码 <p class="text-danger">ngModel只能用在表单类的元素上面</p>
<input [(ngModel)]="currentRace.name">
<p>{{currentRace.name}}</p> [AppleScript] 纯文本查看 复制代码 public currentRace:any={name:"随机种族"};
请注意,如果你需要使用 NgModel 来进行双向数据绑定,必须要在对应的模块里面 import FormsModule。
1.7 管道
管道的一个典型作用是用来格式化数据,来一个最简单的例子:
[AppleScript] 纯文本查看 复制代码 {{currentTime | date:'yyyy-MM-dd HH:mm:ss'}} [AppleScript] 纯文本查看 复制代码 public currentTime: Date = new Date();
Angular里面一共内置了12个管道:
管道还有另一个典型的作用,就是用来做国际化,后面有一个独立的小节专门演示 Angular 的国际化写法。
|
|