黑马程序员技术交流社区

标题: 【太原校区】零基础学习angular5.0(五)【每周更新】 [打印本页]

作者: 郝永亮    时间: 2018-1-10 15:16
标题: 【太原校区】零基础学习angular5.0(五)【每周更新】
本帖最后由 郝永亮 于 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>
    <li *ngFor="let race of races">
        {{race.name}}
    </li>
</ul>

为什么要“轻逻辑”?
很明显,浏览器不认识 *ngFor 和 {{...}} 这种语法,所以必须在浏览器里面进行“编译”,获得对应的模板函数,然后再把数据传递给模板函数,最终结合起来获得一堆 HTML 标签,然后才能把这一堆标签插入到 DOM 树里面去。

如果启用了 AOT,处理的步骤有一些变化,@angular/cli 会对模板进行“静态编译”,避免在浏览器里面动态编译的过程。

而 Handlebars 这种模板引擎完全是运行时编译模板字符串的,你可以编写以下代码
[AppleScript] 纯文本查看 复制代码
//定义模板字符串
var source=`
    <ul>
        {{#each races}}
            <li>{{name}}</li>
        {{/each}}
    </ul>
    `;

//在运行时把模板字符串编译成JS函数
var templateFn=Handlebars.compile(source);

//把数据传给模板函数,获得最终的HTML
var html=templateFn([
    {name:'人族'},
    {name:'神族'},
    {name:'虫族'}
]);

注意到 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{
    return 65535;
}

1.2.4模板内的局部变量
[AppleScript] 纯文本查看 复制代码
<input type="text" #heroInput>
<p>{{heroInput.value}}</p>
<p>{{heroInput.text}}</p>

    其中input标签中的heroInput是局部变量名,#开头,表示当前input对象

    模板局部变量 > 指令中的同名变量 > 组件中的同名属性。

    这种优先级规则和 JSP 里面的变量取值规则非常类似,对比一下很好理解对不对?你可以自己写代码测试一下。


1.3 属性绑定

属性绑定是用方括号来做的,写法:
[AppleScript] 纯文本查看 复制代码
<img [src]="imgSrc" />
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{
    alert("测试事件绑定!");
}


1.5 双向绑定
双向绑定是通过方括号里面套一个圆括号来做的,模板写法:
[AppleScript] 纯文本查看 复制代码
<input  type="text" [(ngModel)] = "dobuleBindName"/>
{{dobuleBindName}}

对应组件内部的属性定义:
[AppleScript] 纯文本查看 复制代码
private dobuleBindName:String = "";



AngularJS 是第一个把“双向数据绑定”这个特性带到前端来的框架,这也是 AngularJS 当年最受开发者追捧的特性,之一。


根据 AngularJS 团队当年讲的故事,“双向数据绑定”这个特性可以大幅度压缩前端代码的规模。大家可以回想一下 jQuery 时代的做法,如果要实现类似的效果,是不是要自己去编写大量的代码?尤其是那种大规模的表单,一大堆的赋值和取值操作,都是非常丑陋的“面条”代码,而有了“双向数据绑定”特性之后,一个绑定表达式就搞定。


目前,主流的几款前端框架都已经接受了“双向数据绑定”这个特性。


当然,也有一些人不喜欢“双向数据绑定”,还有人专门写了文章来进行批判,也算是前端一景。


1.5 在模板里面使用结构型指令
Angular 有3个内置的结构型指令:*ngIf、*ngFor、ngSwitch。ngSwitch 的语法比较啰嗦,使用频率小一些。


*ngIf 代码实例:
[AppleScript] 纯文本查看 复制代码
<div *ngIf="showed" >显示/不显示</div>
<button type="button" (click) = "toggleShow()">切换</button>

[AppleScript] 纯文本查看 复制代码
private  showed:boolean = false;
public toggleShow():void{
   this.showed = !this.showed;
}


*ngFor代码示例
[AppleScript] 纯文本查看 复制代码
<div *ngFor="let fruit of fruits;let i = index; ">
    <div>{{i+1}}-{{fruit}}</div>
</div>


[AppleScript] 纯文本查看 复制代码
private fruits:Array<any> = ['苹果', '香蕉', '樱桃'] ;

*ngSwitch 代码实例:
[AppleScript] 纯文本查看 复制代码
<div [ngSwitch]="mapStatus">
    <p *ngSwitchCase="0">下载中...</p>
    <p *ngSwitchCase="1">正在读取...</p>
    <p *ngSwitchDefault>系统繁忙...</p>
</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>
<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 的国际化写法。

作者: renhua    时间: 2018-1-10 16:10

作者: 漏网之鱼    时间: 2018-1-10 16:28
膜拜大牛




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2