前言
我這個(gè)人令野,寫文章或者說(shuō)心得,不喜歡直接抄官網(wǎng)上面的東西忆矛,實(shí)在是沒(méi)啥意思。我還是喜歡用我的大白話來(lái)寫文章请垛。今天這個(gè)關(guān)于模板輸入變量的這個(gè)我今天啃官網(wǎng)啃了許久催训,總算是初步的了解了是啥東西。
let-變量
到底是什么
我想研究這玩意的起因在于之前我使用ng-zorro的時(shí)候宗收,用到了它的分頁(yè)組件Pagination
(官網(wǎng)鏈接)漫拭。這其中有一個(gè)自定義上一頁(yè)下一頁(yè)模板的功能。代碼的話如下:
@Component({
selector: 'nz-demo-pagination-item-render',
template: `
<nz-pagination [nzPageIndex]="1" [nzTotal]="500" [nzItemRender]="renderItemTemplate"></nz-pagination>
<ng-template #renderItemTemplate let-type let-page="page">
<ng-container [ngSwitch]="type">
<a *ngSwitchCase="'page'">{{ page }}</a>
<a *ngSwitchCase="'prev'">Previous</a>
<a *ngSwitchCase="'next'">Next</a>
<a *ngSwitchCase="'prev_5'"><<</a>
<a *ngSwitchCase="'next_5'">>></a>
</ng-container>
</ng-template>
`
})
export class NzDemoPaginationItemRenderComponent {}
看完這個(gè)之后我就很是疑惑混稽,這個(gè)let是啥采驻,為啥let-
跟上一個(gè)變量之后就有值了呢?然后我就開始在官網(wǎng)中找這個(gè)let
是什么東西匈勋。最終礼旅,我在主要概念-指令-結(jié)構(gòu)性指令的微語(yǔ)法一節(jié)找到了關(guān)于let
的說(shuō)明。官網(wǎng)描述:微語(yǔ)法洽洁。
在下面還有一段簡(jiǎn)短的說(shuō)明:
模板輸入變量(Template input variable)
模板輸入變量是這樣一種變量痘系,你可以在單個(gè)實(shí)例的模板中引用它的值。 這個(gè)例子中有好幾個(gè)模板輸入變量:
hero
饿自、i
和odd
汰翠。 它們都是用let
作為前導(dǎo)關(guān)鍵字临谱。......
你使用
let
關(guān)鍵字(如let hero
)在模板中聲明一個(gè)模板輸入變量。 這個(gè)變量的范圍被限制在所重復(fù)模板的單一實(shí)例上奴璃。 事實(shí)上,你可以在其它結(jié)構(gòu)型指令中使用同樣的變量名城豁。......
官網(wǎng)還是一如既往的不說(shuō)人話苟穆,短短幾句話就給你介紹完了,也不告訴你這玩意怎么用唱星,也不告訴你let
聲明的變量的值到底是從哪里來(lái)的雳旅。我越看越氣,得间聊,官網(wǎng)你不說(shuō)攒盈,我自己找。
先說(shuō)一個(gè)粗略的結(jié)論:let聲明的變量是這個(gè)template模板的上下文對(duì)象中的變量哎榴。不然為啥叫模板輸入變量呢型豁。在*ngFor內(nèi)幕這小節(jié)中,我們了解到了其內(nèi)幕尚蝌,結(jié)構(gòu)性指令其實(shí)就是將宿主元素包裹在一個(gè)<ng-template></ng-template>
中迎变,然后在這個(gè)模板上將*ngFor
中的表達(dá)式解析成一個(gè)個(gè)的let
模板輸入變量和這個(gè)指令需要傳入的值,官方示例代碼如下:
//解析前的模板
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
//angular解析后的模板
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
- 在此提示一下飘言,所謂的宿主元素就是指令所在的那個(gè)元素衣形,像上面的例子中,
div
就是*ngFor
指令的宿主元素姿鸿。 -
trackBy
這個(gè)類似于vue和react中的key谆吴。可以給模板一個(gè)標(biāo)識(shí)苛预,使其重新渲染的時(shí)候性能開銷降低一些句狼。
然后我們?cè)倏垂倬W(wǎng)說(shuō)的:
let-i
和let-odd
變量是通過(guò)let i=index
和let odd=odd
來(lái)定義的。 Angular 把它們?cè)O(shè)置為上下文對(duì)象中的index
和odd
屬性的當(dāng)前值碟渺。- 這里并沒(méi)有指定
let-hero
的上下文屬性鲜锚。它的來(lái)源是隱式的。 Angular 將let-hero
設(shè)置為此上下文中$implicit
屬性的值苫拍, 它是由NgFor
用當(dāng)前迭代中的英雄初始化的芜繁。
看完這段描述,我們可以得知:angular為這個(gè)模板設(shè)置了上下文對(duì)象绒极。但是我們看不到這個(gè)過(guò)程骏令,因?yàn)檫@是在ngFor
的源碼內(nèi)部實(shí)現(xiàn)的。并且這個(gè)上下文對(duì)象具備index
和odd
屬性垄提,并且包含一個(gè)$implicit
(implicit:含蓄的榔袋;不直接言明的)的屬性周拐。那么我們推斷這個(gè)上下文對(duì)象至少有以下幾個(gè)屬性:
{
$implicit:null,
index:null,
odd:null,
}
那么我們聲明let
變量的本質(zhì)其實(shí)就是聲明一個(gè)變量獲取上下文對(duì)象中的同名屬性的值。let-hero
不進(jìn)行任何賦值的話凰兑,hero
默認(rèn)等于$implicit
的值妥粟。無(wú)論是有多少個(gè)let-a
,let-b
吏够,let-c
還是let-me
勾给。聲明的這個(gè)變量的值都等于$implicit
的值。而我們進(jìn)行賦值過(guò)的锅知,比如let-i = "index"
播急,i
的值就等于這個(gè)上下文對(duì)象中的index
屬性對(duì)應(yīng)的值。
上下文對(duì)象是如何設(shè)置的
當(dāng)我們知道這個(gè)上下文對(duì)象是什么了售睹,就該想這個(gè)上下文對(duì)象是怎么設(shè)置的了桩警。
在結(jié)構(gòu)性指令這一節(jié)當(dāng)中,我們跟著官方示例做了一遍這個(gè)自定義結(jié)構(gòu)性指令(如果還沒(méi)有做的話昌妹,建議先跟著做一遍)捶枢。在UnlessDirective
這個(gè)指令中,其構(gòu)造器constructor
聲明了兩個(gè)可注入的變量飞崖,分別是TemplateRef
和ViewContainerRef
柱蟀。官網(wǎng)給的解釋我覺得太過(guò)晦澀難懂,我這里給出一下我自己的理解:TemplateRef
代表的是宿主元素被包裹之后形成的模板的引用蚜厉。而ViewContainerRef
代表的是一個(gè)視圖容器的引用长已。那么問(wèn)題來(lái)了,這個(gè)視圖容器在哪兒呢昼牛?我們?cè)?code>constructor構(gòu)造器中打印一下ViewContainerRef
术瓮。打印結(jié)果如圖:
然后我們點(diǎn)進(jìn)去這個(gè)comment
元素。發(fā)現(xiàn)其就是一個(gè)注釋元素贰健。如圖所示:
其實(shí)我也不是很確定這個(gè)視圖容器到底是不是這個(gè)注釋元素胞四。但是毋庸置疑的是,視圖容器和宿主元素是兄弟關(guān)系伶椿,緊挨著宿主元素辜伟。我們可以使用ViewContainerRef
中的createEmbeddedView()
方法(Embedded:嵌入式,內(nèi)嵌式)脊另,將templateRef
模板引用傳入進(jìn)去导狡,創(chuàng)建出來(lái)一個(gè)真實(shí)的視圖。由于這個(gè)視圖是被插入到視圖容器ViewContainerRef
中了偎痛,所以又叫內(nèi)嵌視圖旱捧。那么這又和我們的上下文對(duì)象有什么關(guān)系呢?其實(shí)createEmbeddedView
這個(gè)方法不止一個(gè)參數(shù),其第二個(gè)參數(shù)就是給我們的模板設(shè)置上下文對(duì)象的枚赡。API的詳情介紹請(qǐng)看createEmbeddedView這個(gè)API的詳情氓癌。
就這樣。我們就可以將上下文對(duì)象塞入模板中了贫橙,這樣的話我們也可以直接使用let聲明變量的方法來(lái)使用這個(gè)上下文對(duì)象了贪婉。
自定義一個(gè)簡(jiǎn)單的類*ngFor指令——appRepeat
那么我們知道是如何設(shè)置的了,那么我們就來(lái)驗(yàn)證一下是否是對(duì)的卢肃。接下來(lái)谓松,我們仿照ngfor
的功能,自己寫一個(gè)簡(jiǎn)單的渲染指令践剂。
首先我們定義一個(gè)指令:RepeatDirective
。代碼如下:
@Directive({
selector: '[appRepeat]',
})
export class RepeatDirective {
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
) { }
@Input() set appRepeatOf(heroesList: string[]) {
heroesList.forEach((item, index, arr) => {
this.viewContainer.createEmbeddedView(this.templateRef, {
//當(dāng)前條目的默認(rèn)值
$implicit: item,
//可迭代對(duì)象的總數(shù)
count: arr.length,
//當(dāng)前條目的索引值
index: index,
//如果當(dāng)前條目在可迭代對(duì)象中的索引號(hào)為偶數(shù)則為 true娜膘。
even: index % 2 === 0,
//如果當(dāng)前條目在可迭代對(duì)象中的索引號(hào)為奇數(shù)則為 true逊脯。
odd: index % 2 === 1,
});
});
}
}
然后我們將其導(dǎo)入NgModule中,這個(gè)過(guò)程就省略不寫了竣贪。然后我們?cè)诮M件中使用一下這個(gè)指令:
@Component({
selector: 'app-structural-likeNgFor-demo',
template: `
<h2>原神1.5版本卡池角色</h2>
<h4>自定義ngFor(appRepeat)</h4>
<ul>
<li *appRepeat="let h of heroesList;let i = index;let even = even">
索引:{{i}} -- {{h}} -- 索引值是否是偶數(shù):{{even.toString()}}
</li>
</ul>
<h4>真正的ngFor</h4>
<ul>
<li *ngFor="let h of heroesList;let i = index;let even = even">
索引:{{i}} -- {{h}} -- 索引值是否是偶數(shù):{{even.toString()}}
</li>
</ul>
`,
})
export class StructuralLikeNgForDemoComponent {
public heroesList: string[] = ['鐘離', '煙緋', '諾艾爾', '迪奧娜'];
}
在這里需要注意的是指令中的appRepeatOf
不是亂寫的军洼。在微語(yǔ)法的解析過(guò)程中let h of heroesList
中的of
首先首字母會(huì)變成大寫的,變成Of
演怎。然后在給它加上這個(gè)指令的前綴匕争,也就是appRepeat
。組合起來(lái)就是appRepeatOf
了爷耀。由它來(lái)接收一個(gè)可迭代的對(duì)象甘桑。
最后顯示的效果圖:
運(yùn)行結(jié)果的話和*ngFor
沒(méi)有區(qū)別。但是功能肯定是欠缺的歹叮,如果有能力的小伙伴可以去閱讀*ngFor
的源碼:*ngFor的源碼跑杭。
總結(jié)
通過(guò)這篇文章,我們知道了let-變量
這個(gè)模板輸入變量是通過(guò)模板的上下文對(duì)象中定義并獲取值的咆耿。然后想要設(shè)置上下文對(duì)象的話需要通過(guò)createEmbeddedView
方法的第二個(gè)參數(shù)來(lái)設(shè)置德谅。
結(jié)語(yǔ)
但是我總覺得了解的還不夠透徹,我總覺得設(shè)置模板的上下文對(duì)象可能不只是createEmbeddedView
這一種方法萨螺,但是我并沒(méi)有找到其他的方法窄做。如果各位小伙伴有知道其他的方法可以留言告訴我。
參考資料:
這篇文章靈感來(lái)自:Angular 實(shí)現(xiàn)一個(gè)"repeat" 指令