一般來說,我們編寫組件或者抽取組件的時候,總是會考慮到組件的復(fù)用性以及擴展性。當(dāng)我們想讓組件變成嵌套關(guān)系的時候,我們就需要考慮這個子組件是寫死在父組件下面?還是抽取出來作為一個外部組件然后插入到父組件中?其實這個主要是看個人選擇了。在angular中我們可以使用這兩種方式封裝組件。而ViewChild和contentChild這兩個裝飾器官網(wǎng)上面的定義是這樣的:
ViewChild:屬性裝飾器,用于配置一個視圖查詢。 變更檢測器會在視圖的 DOM 中查找能匹配上該選擇器的第一個元素或指令。 如果視圖的 DOM 發(fā)生了變化,出現(xiàn)了匹配該選擇器的新的子節(jié)點,該屬性就會被更新。
ContentChild:用于配置內(nèi)容查詢的參數(shù)裝飾器。用于從內(nèi)容 DOM 獲取與此選擇器匹配的第一個元素或指令。如果內(nèi)容 DOM 發(fā)生了更改,并且有一個新的子項與選擇器匹配,則該屬性將被更新。
其實,這兩個裝飾器其實就是為了獲取子組件的引用而已,相當(dāng)于vue和react中的ref。只不過angular這個可以通過ViewChildren或者ContentChildren來獲取多個引用。這個所謂的引用或者angular的視圖查詢或者內(nèi)容查詢獲取到的值其實是這個子組件生成出來的實例。我們可以像使用vue或者react似的使用這兩個查詢來直接修改子組件的屬性或者調(diào)用子組件的方法。
接下來我會舉一個例子來說明這兩個(或者說四個)裝飾器的用法。
-
現(xiàn)在我們有一個業(yè)務(wù)場景:假設(shè)有一個新聞列表:
<ul> <li> <span>1</span> <span>【原神】鐘離將在1.5版本復(fù)刻,大家準(zhǔn)備好原石了嗎?</span> <span>2021/4/16</span> </li> </ul>li中有三個span,分別表示新聞id,新聞標(biāo)題,新聞時間。我們現(xiàn)在的需求是,將這個組件抽離出來。然后將li的部分封裝成一個子組件。如下: -
首先,我們封裝這個子組件
<news-item>import {Component, Input} from '@angular/core'; import {New} from './models'; @Component({ selector:'news-item', template:` <li [style]="{display:'flex',justifyContent:'center',alignItems:'center'}"> <p>id:{{Item.id}}</p> <p>{{Item.title}}</p> <p>{{Item.time}}</p> </li> `, ] }) export class NewsItem { @Input() Item:New; constructor() { } }然后定義一下New
export interface New { id:number; title:string, time:string; } -
然后,我們將
ul整體封裝成一個父組件。然后使用兩種不同的方式來封裝。第一種方法:將子組件封裝進父組件中;
第二種方法:子組件獨立在外,使用的時候才將其放入父組件的標(biāo)簽之內(nèi);
-
先用第一種方法:
NewsListByViewChild.ts代碼如下:import {AfterViewInit, Component, Input, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {New} from './models'; import {NewsItem} from './NewsItem'; @Component({ selector:'news-list-view', template:` <ul> <news-item *ngFor="let n of NewsList" [Item]="n"></news-item> </ul> ` }) export class NewsListByViewChild implements AfterViewInit{ public NewsList:New[] = [ { id:1, title:'view-標(biāo)題1', time:'2021/4/17' }, { id:2, title:'view-標(biāo)題2', time:'2021/4/19' }, { id:3, title:'view-標(biāo)題3', time:'2021/5/6' } ] //使用viewchild獲取單個的符合條件的組件或者指令 @ViewChild(NewsItem) newItem:NewsItem; //使用viewChildren獲取多個的符合條件的組件或者指令,QueryList是一個類似于數(shù)組的可迭代對象,我們可以使用get([索引值])的方法來取得每一個元素 @ViewChildren(NewsItem) allNewsItem:QueryList<NewsItem> constructor() { } //ViewChild設(shè)置的查詢,只會在AfterViewInit之后的生命周期訪問到。 ngAfterViewInit() { console.log('newItem:',this.newItem); console.log('allNewsItem:',this.allNewsItem); //使用get()來獲取單獨的元素 console.log('allNewsItem01:',this.allNewsItem.get(0)); console.log('allNewsItem02:',this.allNewsItem.get(1)); console.log('allNewsItem03:',this.allNewsItem.get(2)); //然后我們可以直接調(diào)用子組件的屬性和方法。 setTimeout(()=>{ this.newItem.Item.id = 99; this.allNewsItem.get(1).title = '【原神】新角色優(yōu)菈閃亮登場!' }) } }將其導(dǎo)入使用:
app.component.ts中調(diào)用@Component({ selector: 'app-root', template: ` <news-list-view></news-list-view> `, }) export class AppComponent {} -
第二種方法:
NewsListByContentChild.ts代碼如下:import {AfterContentInit, Component, ContentChild, ContentChildren, QueryList} from '@angular/core'; import {NewsItem} from './NewsItem'; @Component({ selector:'news-list-content', template:` <ul> <ng-content></ng-content> </ul> ` }) export class NewsListByContentChild implements AfterContentInit{ @ContentChild(NewsItem) newsItem:NewsItem; @ContentChildren(NewsItem) allNewsItem:QueryList<NewsItem> constructor() {} //ContentChild設(shè)置的查詢,只會在調(diào)用AfterContentInit之后才能訪問到 ngAfterContentInit() { console.log("NewsItem:",this.newsItem); console.log("allNewsItem:",this.allNewsItem); } }然后將其導(dǎo)入
app.component.ts中使用//導(dǎo)入語句省略 @Component({ selector: 'app-root', template: ` <news-list-content> <news-item *ngFor="let n of NewsList" [Item]="n"></news-item> </news-list-content> `, }) export class AppComponent { public NewsList:New[] = [ { id:4, title:'content-標(biāo)題1', time:'2021/3/21' }, { id:5, title:'content-標(biāo)題2', time:'2021/4/23' }, { id:6, title:'content-標(biāo)題3', time:'2021/5/30' } ] }
總結(jié):
ViewChild和ContentChild一開始了解不了就是因為一開始沒有找到在vue或者react中對應(yīng)什么api。其實就是相當(dāng)于ref。只不過是寫法有不同而已。當(dāng)我們熟悉了之后,使用這兩個(或者四個)其實就得心應(yīng)手了。