Angular [組件]模板的使用

模板是編寫 Angular 組件最重要的一環(huán),你必須深入理解以下知識點(diǎn)才能玩轉(zhuǎn) Angular 模板:

對比各種 JS 模板引擎的設(shè)計(jì)思路

Mustache(八字胡)語法

模板內(nèi)的局部變量

屬性綁定似将、事件綁定、雙向綁定

在模板里面使用結(jié)構(gòu)型指令 *ngIf颤介、*ngFor、ngSwitch

在模板里面使用屬性型指令 NgClass、NgStyle、NgModel

在模板里面使用管道格式化數(shù)據(jù)

一些小 feature:安全導(dǎo)航顽爹、非空斷言

“深入理解”的含義是:你需要很自如地運(yùn)用這些 API纤泵,寫代碼的時(shí)候不翻閱 API 文檔骆姐。

因?yàn)楹芏嘈率种跃幋a效率不高,其中一個(gè)主要的原因就是在編碼過程中不停翻文檔捏题、查資料玻褪。

對比各種 JS 模板引擎的設(shè)計(jì)思路

幾乎每一款前端框架都會(huì)提供自己的模板語法:

在 jQuery 如日中天的時(shí)代,有 Handlebars 那種功能超強(qiáng)的模板

React 推崇 JSX 模板語法

當(dāng)然還有 Angular 提供的那種與“指令”緊密結(jié)合的模板語法

綜合來說公荧,無論是哪一種前端模板带射,大家都比較推崇“輕邏輯”(logic-less)的設(shè)計(jì)思路。

何為“輕邏輯”循狰?

簡而言之窟社,所謂“輕邏輯”就是說,你不能在模板里面編寫非常復(fù)雜的 JavaScript 表達(dá)式绪钥。比如灿里,Angular 的模板語法就有規(guī)定:

你不能在模板里面 new 對象

不能使用 =、+=程腹、-= 這類的表達(dá)式

不能用 ++匣吊、-- 運(yùn)算符

不能使用位運(yùn)算符

為什么要“輕邏輯”?

最重要的原因是怕影響運(yùn)行性能,因?yàn)槟0蹇赡軙?huì)被執(zhí)行很多次色鸳。

比如你編寫了以下 Angular 模板:

<ul>

? ? <li *ngFor="let race of races">

? ? ? ? {{race.name}}

? ? </li>

</ul>

很明顯社痛,瀏覽器不認(rèn)識 *ngFor 和 {{…}} 這種語法,因此必須在瀏覽器里面進(jìn)行“編譯”命雀,獲得對應(yīng)的模板函數(shù)蒜哀,然后再把數(shù)據(jù)傳遞給模板函數(shù),最終結(jié)合起來獲得一堆 HTML 標(biāo)簽吏砂,然后才能把這一堆標(biāo)簽插入到 DOM 樹里面去凡怎。

如果啟用了 AOT,處理的步驟有一些變化赊抖,@angular/cli 會(huì)對模板進(jìn)行“靜態(tài)編譯”统倒,避免在瀏覽器里面動(dòng)態(tài)編譯的過程。

而 Handlebars 這種模板引擎完全是運(yùn)行時(shí)編譯模板字符串的氛雪,你可以編寫以下代碼:

//定義模板字符串

var source=`

<ul>

? ? {{#each races}}

? ? ? ? <li>{{name}}</li>

? ? {{/each}}

</ul>

`;

//在運(yùn)行時(shí)把模板字符串編譯成 JS 函數(shù)

var templateFn=Handlebars.compile(source);

//把數(shù)據(jù)傳給模板函數(shù)房匆,獲得最終的 HTML

var html=templateFn([

? ? {name:'人族'},

? ? {name:'神族'},

? ? {name:'蟲族'}

]);

注意到 Handlebars.compile 這個(gè)調(diào)用了吧?這個(gè)地方的本質(zhì)是在運(yùn)行時(shí)把模板字符串“編譯”成了一個(gè) JS 函數(shù)报亩。

鑒于 JS 解釋執(zhí)行的特性浴鸿,你可能會(huì)擔(dān)憂這里會(huì)有性能問題。這種擔(dān)憂是合理的弦追,但是 Handlebars 是一款非常優(yōu)秀的模板引擎岳链,它在內(nèi)部做了各種優(yōu)化和緩存處理。模板字符串一般只會(huì)在第一次被調(diào)用的時(shí)候編譯一次劲件,Handlebars 會(huì)把編譯好的函數(shù)緩存起來掸哑,后面再次調(diào)用的時(shí)候會(huì)從緩存里面獲取,而不會(huì)多次進(jìn)行“編譯”零远。

上面我們多次提到了“編譯”這個(gè)詞苗分,因此很顯然這里有一個(gè)東西是無法避免的,那就是我們必須提供一個(gè) JS 版的“編譯器”牵辣,讓這個(gè)“編譯器”運(yùn)行在瀏覽器里面摔癣,這樣才能在運(yùn)行時(shí)把用戶編寫的模板字符串“編譯”成模板函數(shù)。

有一些模板引擎會(huì)真的去用 JS 編寫一款“編譯器”出來纬向,比如 Angular 和 Handlebars择浊,它們都真的編寫了一款 JS(TS)版的編譯器。而有一些簡單的模板引擎逾条,例如 Underscore 里面的模板函數(shù)琢岩,只是用正則表達(dá)式做了字符串替換而已,顯得特別簡陋膳帕。這種簡陋的模板引擎對模板的寫法有非常多的限制粘捎,因?yàn)樗皇钦嬲木幾g器薇缅,能支持的語法特性非常有限。

因此攒磨,評估一款模板引擎的強(qiáng)弱泳桦,最核心的東西就是評估它的“編譯器”做得怎么樣。但是不管怎么說娩缰,畢竟是 JS 版的“編譯器”灸撰,我們不可能把它做得像 G++ 那么強(qiáng)大,也沒有必要做得那么強(qiáng)大拼坎,因?yàn)檫@個(gè) JS 版的編譯器需要在瀏覽器里面運(yùn)行浮毯,搞得太復(fù)雜瀏覽器拖不動(dòng)!

以上就是為什么大多數(shù)模板引擎都要強(qiáng)調(diào)“輕邏輯”的最根本原因泰鸡。

對于 Angular 來說债蓝,強(qiáng)調(diào)“輕邏輯”還有另一個(gè)原因:在組件的整個(gè)生命周期里面,模板函數(shù)會(huì)被執(zhí)行很多次盛龄。你可以想象饰迹,Angular 每次要刷新組件外觀的時(shí)候,都需要去調(diào)用一下模板函數(shù)余舶,如果你在模板里面編寫了非常復(fù)雜的代碼啊鸭,一定會(huì)增加渲染時(shí)間,用戶一定會(huì)感到界面有“卡頓”匿值。

人眼的視覺延遲大約是 100ms 到 400ms 之間赠制,如果整個(gè)頁面的渲染時(shí)間超過 400ms,界面基本上就卡得沒法用了挟憔。有一些做游戲的開發(fā)者會(huì)追求 60fps 刷新率的細(xì)膩感覺钟些,60 分之 1 秒約等于 16.7ms,如果 UI 整體的渲染時(shí)間超過了 16.7ms曲楚,就沒法達(dá)到這個(gè)要求了厘唾。

輕邏輯(logic-less)帶來了效率的提升褥符,也帶來了一些不方便龙誊,比如很多模板引擎都實(shí)現(xiàn)了 if 語句,但是沒有實(shí)現(xiàn) else喷楣,因此開發(fā)者們在編寫復(fù)雜業(yè)務(wù)邏輯的時(shí)候模板代碼會(huì)顯得非常啰嗦趟大。

目前來說,并沒有完美的方案能同時(shí)兼顧運(yùn)行效率和語法表現(xiàn)能力铣焊,這里只能取一個(gè)平衡逊朽。

Mustache 語法

Mustache 語法也就是你們說的雙花括號語法 {{…}},老外覺得它像八字胡子曲伊,很奇怪啊叽讳,難道老外喜歡側(cè)著頭看東西追他?

好消息是,很多模板引擎都接受了 Mustache 語法岛蚤,這樣一來學(xué)習(xí)量又降低了不少邑狸,開心吧?

關(guān)于 Mustache 語法谤逼,你需要掌握 3 點(diǎn):

它可以獲取到組件里面定義的屬性值

它可以自動(dòng)計(jì)算簡單的數(shù)學(xué)表達(dá)式瑰剃,如加減乘除羡棵、取模

它可以獲得方法的返回值

請依次看例子。

插值語法關(guān)鍵代碼實(shí)例:

<h3>

? ? 歡迎來到{{title}}硅堆!

</h3>

public title = '假的星際爭霸2';

簡單的數(shù)學(xué)表達(dá)式求值:

<h3>1+1={{1+1}}</h3>

調(diào)用組件里面定義的方法:

<h3>可以調(diào)用方法{{getVal()}}</h3>

public getVal():any{

? ? return 65535;

}

模板內(nèi)的局部變量

<input #heroInput>

<p>{{heroInput.value}}</p>

有一些朋友會(huì)追問,如果我在模板里面定義的局部變量和組件內(nèi)部的屬性重名會(huì)怎么樣呢贿讹?

如果真的出現(xiàn)了重名渐逃,Angular 會(huì)按照以下優(yōu)先級來進(jìn)行處理:

模板局部變量 > 指令中的同名變量 > 組件中的同名屬性。

復(fù)制

這種優(yōu)先級規(guī)則和 JSP 里面的變量取值規(guī)則非常類似民褂,對比一下很好理解對不對朴乖?你可以自己寫代碼測試一下。

值綁定

值綁定是用方括號來做的助赞,寫法:

<img [src]="imgSrc" />

public imgSrc:string="./assets/imgs/1.jpg";

很明顯买羞,這種綁定是單向的。

事件綁定

事件綁定是用圓括號來做的雹食,寫法:

<button class="btn btn-success" (click)="btnClick($event)">測試事件</button>

對應(yīng) Component 內(nèi)部的方法定義:

public btnClick(event):void{

? ? alert("測試事件綁定畜普!");

}

雙向綁定

雙向綁定是通過方括號里面套一個(gè)圓括號來做的,模板寫法:

<font-resizer [(size)]="fontSizePx"></font-resizer>

對應(yīng)組件內(nèi)部的屬性定義:

public fontSizePx:number=14;

AngularJS 是第一個(gè)把“雙向數(shù)據(jù)綁定”這個(gè)特性帶到前端來的框架群叶,這也是 AngularJS 當(dāng)年最受開發(fā)者追捧的特性吃挑,之一。

根據(jù) AngularJS 團(tuán)隊(duì)當(dāng)年講的故事街立,“雙向數(shù)據(jù)綁定”這個(gè)特性可以大幅度壓縮前端代碼的規(guī)模舶衬。大家可以回想一下 jQuery 時(shí)代的做法,如果要實(shí)現(xiàn)類似的效果赎离,是不是要自己去編寫大量的代碼逛犹?尤其是那種大規(guī)模的表單,一大堆的賦值和取值操作梁剔,都是非常丑陋的“面條”代碼虽画,而有了“雙向數(shù)據(jù)綁定”特性之后,一個(gè)綁定表達(dá)式就搞定荣病。

目前码撰,主流的幾款前端框架都已經(jīng)接受了“雙向數(shù)據(jù)綁定”這個(gè)特性。

當(dāng)然个盆,也有一些人不喜歡“雙向數(shù)據(jù)綁定”脖岛,還有人專門寫了文章來進(jìn)行批判朵栖,也算是前端一景。

在模板里面使用結(jié)構(gòu)型指令

Angular 有 3 個(gè)內(nèi)置的結(jié)構(gòu)型指令:*ngIf柴梆、*ngFor混槐、ngSwitch。ngSwitch 的語法比較啰嗦轩性,使用頻率小一些声登。

*ngIf 代碼實(shí)例:

<p *ngIf="isShow" style="background-color:#ff3300">顯示還是不顯示?</p>

<button class="btn btn-success" (click)="toggleShow()">控制顯示隱藏</button>

public isShow:boolean=true;

public toggleShow():void{

? ? this.isShow=!this.isShow;

}

*ngFor 代碼實(shí)例:

<li *ngFor="let race of races;let i=index;">

? ? {{i+1}}-{{race.name}}

</li>

public races:Array=[

? ? {name:"人族"},

? ? {name:"蟲族"},

? ? {name:"神族"}

];

*ngSwitch 代碼實(shí)例:

<div [ngSwitch]="mapStatus">

? ? <p *ngSwitchCase="0">下載中...</p>

? ? <p *ngSwitchCase="1">正在讀取...</p>

? ? <p *ngSwitchDefault>系統(tǒng)繁忙...</p>

</div>

public mapStatus:number=1;

特別注意:一個(gè) HTML 標(biāo)簽上只能同時(shí)使用一個(gè)結(jié)構(gòu)型的指令揣苏。

因?yàn)椤敖Y(jié)構(gòu)型”指令會(huì)修改 DOM 結(jié)構(gòu)悯嗓,如果在一個(gè)標(biāo)簽上使用多個(gè)結(jié)構(gòu)型指令,大家都一起去修改 DOM 結(jié)構(gòu)卸察,到時(shí)候到底誰說了算脯厨?

那么需要在同一個(gè) HTML 上使用多個(gè)結(jié)構(gòu)型指令應(yīng)該怎么辦呢?有兩個(gè)辦法:

加一層空的 div 標(biāo)簽

加一層 <ng-container>

在模板里面使用屬性型指令

使用頻率比較高的 3 個(gè)內(nèi)置指令是:NgClass坑质、NgStyle合武、NgModel。

NgClass 使用案例代碼:

<div [ngClass]="currentClasses">同時(shí)批量設(shè)置多個(gè)樣式</div>

<button class="btn btn-success" (click)="setCurrentClasses()">設(shè)置</button>

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

? ? };

}

.saveable{

? ? font-size: 18px;

}

.modified {

? ? font-weight: bold;

}

.special{

? ? background-color: #ff3300;

}

NgStyle 使用案例代碼:

<div [ngStyle]="currentStyles">

? ? 用NgStyle批量修改內(nèi)聯(lián)樣式涡扼!

</div>

<button class="btn btn-success" (click)="setCurrentStyles()">設(shè)置</button>

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 這種方式相當(dāng)于在代碼里面寫 CSS 樣式稼跳,比較丑陋,違反了注意點(diǎn)分離的原則吃沪,而且將來不太好修改汤善,非常不建議這樣寫。

NgModel 使用案例代碼:

<p class="text-danger">ngModel只能用在表單類的元素上面</p>

? ? <input [(ngModel)]="currentRace.name">

<p>{{currentRace.name}}</p>

public currentRace:any={name:"隨機(jī)種族"};

請注意票彪,如果你需要使用 NgModel 來進(jìn)行雙向數(shù)據(jù)綁定红淡,必須要在對應(yīng)的模塊里面 import FormsModule 。

管道

管道的一個(gè)典型作用是用來格式化數(shù)據(jù)降铸,來一個(gè)最簡單的例子:

{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}

public currentTime: Date = new Date();

Angular 里面一共內(nèi)置了 17 個(gè)指令(有一些已經(jīng)過時(shí)了):

在復(fù)雜的業(yè)務(wù)場景里面在旱,17 個(gè)指令肯定不夠用,如果需要自定義指令推掸,請查看這里的例子: https://angular.io/guide/pipes 桶蝎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市终佛,隨后出現(xiàn)的幾起案子俊嗽,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芯咧,死亡現(xiàn)場離奇詭異竹揍,居然都是意外死亡邪铲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門昧碉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揽惹,你說我怎么就攤上這事被饿。” “怎么了搪搏?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵狭握,是天一觀的道長。 經(jīng)常有香客問我疯溺,道長论颅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任囱嫩,我火速辦了婚禮恃疯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墨闲。我一直安慰自己澡谭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布损俭。 她就那樣靜靜地躺著蛙奖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杆兵。 梳的紋絲不亂的頭發(fā)上雁仲,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音琐脏,去河邊找鬼攒砖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛日裙,可吹牛的內(nèi)容都是我干的吹艇。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼昂拂,長吁一口氣:“原來是場噩夢啊……” “哼受神!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起格侯,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鼻听,失蹤者是張志新(化名)和其女友劉穎财著,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撑碴,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撑教,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醉拓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伟姐。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亿卤,靈堂內(nèi)的尸體忽然破棺而出愤兵,到底是詐尸還是另有隱情,我是刑警寧澤怠噪,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布恐似,位于F島的核電站,受9級特大地震影響傍念,放射性物質(zhì)發(fā)生泄漏矫夷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一憋槐、第九天 我趴在偏房一處隱蔽的房頂上張望双藕。 院中可真熱鬧,春花似錦阳仔、人聲如沸忧陪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘶摊。三九已至,卻和暖如春评矩,著一層夾襖步出監(jiān)牢的瞬間叶堆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工斥杜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虱颗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓蔗喂,卻偏偏與公主長得像忘渔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子缰儿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容