這篇文章給大家介紹Angular中的onPush變更檢測策略有哪些,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
成都創(chuàng)新互聯(lián)專注于海湖新企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè),電子商務(wù)商城網(wǎng)站建設(shè)。海湖新網(wǎng)站建設(shè)公司,為海湖新等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站,專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)

默認(rèn)情況下,Angular使用ChangeDetectionStrategy.Default策略來進(jìn)行變更檢測。
默認(rèn)策略并不事先對應(yīng)用做出任何假設(shè),因此,每當(dāng)用戶事件、記時器、XHR、promise等事件使應(yīng)用中的數(shù)據(jù)將發(fā)生了改變時,所有的組件中都會執(zhí)行變更檢測。
這意味著從點(diǎn)擊事件到從ajax調(diào)用接收到的數(shù)據(jù)之類的任何事件都會觸發(fā)更改檢測。
通過在組件中定義一個getter并且在模板中使用它,我們可以很容易的看出這一點(diǎn):
@Component({
template: `
<h2>Hello {{name}}!</h2>
{{runChangeDetection}}
`
})
export class HelloComponent {
@Input() name: string;
get runChangeDetection() {
console.log('Checking the view');
return true;
}
}@Component({
template: `
<hello></hello>
<button (click)="onClick()">Trigger change detection</button>
`
})
export class AppComponent {
onClick() {}
}執(zhí)行以上代碼后,每當(dāng)我們點(diǎn)擊按鈕時。Angular將會執(zhí)行一遍變更檢測循環(huán),在console里我們可以看到兩行“Checking the view”的日志。
這種技術(shù)被稱作臟檢查。為了知道視圖是否需要更新,Angular需要訪問新值并和舊值比較來判斷是否需要更新視圖。
現(xiàn)在想象一下,如果有一個有成千上萬個表達(dá)式的大應(yīng)用,Angular去檢查每一個表達(dá)式,我們可能會遇到性能上的問題。
那么有沒有辦法讓我們主動告訴Angular什么時候去檢查我們的組件呢?
我們可以將組件的ChangeDetectionStrategy設(shè)置成ChangeDetectionStrategy.OnPush。
這將告訴Angular該組件僅僅依賴于它的@inputs(),只有在以下幾種情況才需要檢查:
Input引用發(fā)生改變通過設(shè)置onPush變更檢測測策略,我們與Angular約定強(qiáng)制使用不可變對象(或稍后將要介紹的observables)。
在變更檢測的上下文中使用不可變對象的好處是,Angular可以通過檢查引用是否發(fā)生了改變來判斷視圖是否需要檢查。這將會比深度檢查要容易很多。
讓我們試試來修改一個對象然后看看結(jié)果。
@Component({
selector: 'tooltip',
template: `
<h2>{{config.position}}</h2>
{{runChangeDetection}}
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipComponent {
@Input() config;
get runChangeDetection() {
console.log('Checking the view');
return true;
}
}@Component({
template: `
<tooltip [config]="config"></tooltip>
`
})
export class AppComponent {
config = {
position: 'top'
};
onClick() {
this.config.position = 'bottom';
}
}這時候去點(diǎn)擊按鈕時看不到任何日志了,這是因?yàn)锳ngular將舊值和新值的引用進(jìn)行比較,類似于:
/** Returns false in our case */
if( oldValue !== newValue ) {
runChangeDetection();
}值得一提的是numbers, booleans, strings, null 、undefined都是原始類型。所有的原始類型都是按值傳遞的. Objects, arrays, 還有 functions 也是按值傳遞的,只不過值是引用地址的副本。
所以為了觸發(fā)對該組件的變更檢測,我們需要更改這個object的引用。
@Component({
template: `
<tooltip [config]="config"></tooltip>
`
})
export class AppComponent {
config = {
position: 'top'
};
onClick() {
this.config = {
position: 'bottom'
}
}
}將對象引用改變后,我們將看到視圖已被檢查,新值被展示出來。
當(dāng)在一個組件或者其子組件中觸發(fā)了某一個事件時,這個組件的內(nèi)部狀態(tài)會更新。 例如:
@Component({
template: `
<button (click)="add()">Add</button>
{{count}}
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
add() {
this.count++;
}
}當(dāng)我們點(diǎn)擊按鈕時,Angular執(zhí)行變更檢測循環(huán)并更新視圖。
你可能會想,按照我們開頭講述的那樣,每一次異步的API都會觸發(fā)變更檢測,但是并不是這樣。
你會發(fā)現(xiàn)這個規(guī)則只適用于DOM事件,下面這些API并不會觸發(fā)變更檢測:
@Component({
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
constructor() {
setTimeout(() => this.count = 5, 0);
setInterval(() => this.count = 5, 100);
Promise.resolve().then(() => this.count = 5);
this.http.get('https://count.com').subscribe(res => {
this.count = res;
});
}
add() {
this.count++;
}注意你仍然是更新了該屬性的,所以在下一個變更檢測流程中,比如去點(diǎn)擊按鈕,count值將會變成6(5+1)。
Angular給我們提供了3種方法來觸發(fā)變更檢測。
第一個是detectChanges()來告訴Angular在該組件和它的子組件中去執(zhí)行變更檢測。
@Component({
selector: 'counter',
template: `{{count}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
count = 0;
constructor(private cdr: ChangeDetectorRef) {
setTimeout(() => {
this.count = 5;
this.cdr.detectChanges();
}, 1000);
}
}第二個是ApplicationRef.tick(),它告訴Angular來對整個應(yīng)用程序執(zhí)行變更檢測。
tick() {
try {
this._views.forEach((view) => view.detectChanges());
...
} catch (e) {
...
}
}第三是markForCheck(),它不會觸發(fā)變更檢測。相反,它會將所有設(shè)置了onPush的祖先標(biāo)記,在當(dāng)前或者下一次變更檢測循環(huán)中檢測。
markForCheck(): void {
markParentViewsForCheck(this._view);
}
export function markParentViewsForCheck(view: ViewData) {
let currView: ViewData|null = view;
while (currView) {
if (currView.def.flags & ViewFlags.OnPush) {
currView.state |= ViewState.ChecksEnabled;
}
currView = currView.viewContainerParent || currView.parent;
}
}需要注意的是,手動執(zhí)行變更檢測并不是一種“hack”,這是Angular有意的設(shè)計(jì)并且是非常合理的行為(當(dāng)然,是在合理的場景下)。
async pipe會訂閱一個 Observable 或 Promise,并返回它發(fā)出的最近一個值。
讓我們看一個input()是observable的onPush組件。
@Component({
template: `
<button (click)="add()">Add</button>
<app-list [items$]="items$"></app-list>
`
})
export class AppComponent {
items = [];
items$ = new BehaviorSubject(this.items);
add() {
this.items.push({ title: Math.random() })
this.items$.next(this.items);
}
}@Component({
template: `
<div *ngFor="let item of _items ; ">{{item.title}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
@Input() items: Observable<Item>;
_items: Item[];
ngOnInit() {
this.items.subscribe(items => {
this._items = items;
});
}
}當(dāng)我們點(diǎn)擊按鈕并不能看到視圖更新。這是因?yàn)樯鲜鎏岬降膸追N情況均未發(fā)生,所以Angular在當(dāng)前變更檢測循環(huán)并不會檢車該組件。
現(xiàn)在,讓我們加上async pipe試試。
@Component({
template: `
<div *ngFor="let item of items | async">{{item.title}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
@Input() items;
}現(xiàn)在可以看到當(dāng)我們點(diǎn)擊按鈕時,視圖也更新了。原因是當(dāng)新的值被發(fā)射出來時,async pipe將該組件標(biāo)記為發(fā)生了更改需要檢查。我們可以在源碼中看到:
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}Angular為我們調(diào)用markForCheck(),所以我們能看到視圖更新了即使input的引用沒有發(fā)生改變。
如果一個組件僅僅依賴于它的input屬性,并且input屬性是observable,那么這個組件只有在它的input屬性發(fā)射一個事件的時候才會發(fā)生改變。
Quick tip:對外部暴露你的subject是不值得提倡的,總是使用asObservable()方法來暴露該observable。
@Component({
selector: 'app-tabs',
template: `<ng-content></ng-content>`
})
export class TabsComponent implements OnInit {
@ContentChild(TabComponent) tab: TabComponent;
ngAfterContentInit() {
setTimeout(() => {
this.tab.content = 'Content';
}, 3000);
}
}@Component({
selector: 'app-tab',
template: `{{content}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
@Input() content;
}<app-tabs> <app-tab></app-tab> </app-tabs>
也許你會以為3秒后Angular將會使用新的內(nèi)容更新tab組件。
畢竟,我們更新來onPush組件的input引用,這將會觸發(fā)變更檢測不是嗎?
然而,在這種情況下,它并不生效。Angular不知道我們正在更新tab組件的input屬性,在模板中定義input()是讓Angular知道應(yīng)在變更檢測循環(huán)中檢查此屬性的唯一途徑。
例如:
<app-tabs> <app-tab [content]="content"></app-tab> </app-tabs>
因?yàn)楫?dāng)我們明確的在模板中定義了input(),Angular會創(chuàng)建一個叫updateRenderer()的方法,它會在每個變更檢測循環(huán)中都對content的值進(jìn)行追蹤。
在這種情況下簡單的解決辦法使用setter然后調(diào)用markForCheck()。
@Component({
selector: 'app-tab',
template: `
{{_content}}
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
_content;
@Input() set content(value) {
this._content = value;
this.cdr.markForCheck();
}
constructor(private cdr: ChangeDetectorRef) {}
}在理解了onPush的強(qiáng)大之后,我們來利用它創(chuàng)造一個更高性能的應(yīng)用。onPush組件越多,Angular需要執(zhí)行的檢查就越少。讓我們看看你一個真是的例子:
我們又一個todos組件,它有一個todos作為input()。
@Component({
selector: 'app-todos',
template: `
<div *ngFor="let todo of todos">
{{todo.title}} - {{runChangeDetection}}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
@Input() todos;
get runChangeDetection() {
console.log('TodosComponent - Checking the view');
return true;
}
}@Component({
template: `
<button (click)="add()">Add</button>
<app-todos [todos]="todos"></app-todos>
`
})
export class AppComponent {
todos = [{ title: 'One' }, { title: 'Two' }];
add() {
this.todos = [...this.todos, { title: 'Three' }];
}
}上述方法的缺點(diǎn)是,當(dāng)我們單擊添加按鈕時,即使之前的數(shù)據(jù)沒有任何更改,Angular也需要檢查每個todo。因此第一次單擊后,控制臺中將顯示三個日志。
在上面的示例中,只有一個表達(dá)式需要檢查,但是想象一下如果是一個有多個綁定(ngIf,ngClass,表達(dá)式等)的真實(shí)組件,這將會非常耗性能。
我們白白的執(zhí)行了變更檢測!
更高效的方法是創(chuàng)建一個todo組件并將其變更檢測策略定義為onPush。例如:
@Component({
selector: 'app-todos',
template: `
<app-todo [todo]="todo" *ngFor="let todo of todos"></app-todo>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
@Input() todos;
}
@Component({
selector: 'app-todo',
template: `{{todo.title}} {{runChangeDetection}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {
@Input() todo;
get runChangeDetection() {
console.log('TodoComponent - Checking the view');
return true;
}
}現(xiàn)在,當(dāng)我們單擊添加按鈕時,控制臺中只會看到一個日志,因?yàn)槠渌膖odo組件的input均未更改,因此不會去檢查其視圖。
并且,通過創(chuàng)建更小粒度的組件,我們的代碼變得更具可讀性和可重用性。
關(guān)于Angular中的onPush變更檢測策略有哪些就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
新聞名稱:Angular中的onPush變更檢測策略有哪些
標(biāo)題路徑:http://www.chinadenli.net/article6/jdgiog.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、靜態(tài)網(wǎng)站、服務(wù)器托管、動態(tài)網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、用戶體驗(yàn)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)