Angular中的onPush變更檢測策略有哪些

這篇文章給大家介紹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ù)

Angular中的onPush變更檢測策略有哪些

默認(rèn)的變更檢測策略

默認(rèn)情況下,Angular使用ChangeDetectionStrategy.Default策略來進(jìn)行變更檢測。

默認(rèn)策略并不事先對應(yīng)用做出任何假設(shè),因此,每當(dāng)用戶事件、記時(shí)器、XHR、promise等事件使應(yīng)用中的數(shù)據(jù)將發(fā)生了改變時(shí),所有的組件中都會(huì)執(zhí)行變更檢測。

這意味著從點(diǎn)擊事件到從ajax調(diào)用接收到的數(shù)據(jù)之類的任何事件都會(huì)觸發(fā)更改檢測。

通過在組件中定義一個(gè)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)擊按鈕時(shí)。Angular將會(huì)執(zhí)行一遍變更檢測循環(huán),在console里我們可以看到兩行“Checking the view”的日志。

這種技術(shù)被稱作臟檢查。為了知道視圖是否需要更新,Angular需要訪問新值并和舊值比較來判斷是否需要更新視圖。

現(xiàn)在想象一下,如果有一個(gè)有成千上萬個(gè)表達(dá)式的大應(yīng)用,Angular去檢查每一個(gè)表達(dá)式,我們可能會(huì)遇到性能上的問題。

那么有沒有辦法讓我們主動(dòng)告訴Angular什么時(shí)候去檢查我們的組件呢?

OnPush變更檢測策略

我們可以將組件的ChangeDetectionStrategy設(shè)置成ChangeDetectionStrategy.OnPush

這將告訴Angular該組件僅僅依賴于它的@inputs(),只有在以下幾種情況才需要檢查:

1. Input引用發(fā)生改變

通過設(shè)置onPush變更檢測測策略,我們與Angular約定強(qiáng)制使用不可變對象(或稍后將要介紹的observables)。

在變更檢測的上下文中使用不可變對象的好處是,Angular可以通過檢查引用是否發(fā)生了改變來判斷視圖是否需要檢查。這將會(huì)比深度檢查要容易很多。

讓我們試試來修改一個(gè)對象然后看看結(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';
  }
}

這時(shí)候去點(diǎn)擊按鈕時(shí)看不到任何日志了,這是因?yàn)锳ngular將舊值和新值的引用進(jìn)行比較,類似于:

/** Returns false in our case */
if( oldValue !== newValue ) { 
  runChangeDetection();
}

值得一提的是numbers, booleans, strings, null 、undefined都是原始類型。所有的原始類型都是按值傳遞的. Objects, arrays, 還有 functions 也是按值傳遞的,只不過值是引用地址的副本。

所以為了觸發(fā)對該組件的變更檢測,我們需要更改這個(gè)object的引用。

@Component({
  template: `
    <tooltip [config]="config"></tooltip>
  `
})
export class AppComponent  {
  config = {
    position: 'top'
  };

  onClick() {
    this.config = {
      position: 'bottom'
    }
  }
}

將對象引用改變后,我們將看到視圖已被檢查,新值被展示出來。

2.源于該組件或其子組件的事件

當(dāng)在一個(gè)組件或者其子組件中觸發(fā)了某一個(gè)事件時(shí),這個(gè)組件的內(nèi)部狀態(tài)會(huì)更新。 例如:

@Component({
  template: `
    <button (click)="add()">Add</button>
    {{count}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0;

  add() {
    this.count++;
  }

}

當(dāng)我們點(diǎn)擊按鈕時(shí),Angular執(zhí)行變更檢測循環(huán)并更新視圖。

你可能會(huì)想,按照我們開頭講述的那樣,每一次異步的API都會(huì)觸發(fā)變更檢測,但是并不是這樣。

你會(huì)發(fā)現(xiàn)這個(gè)規(guī)則只適用于DOM事件,下面這些API并不會(huì)觸發(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++;
  }

注意你仍然是更新了該屬性的,所以在下一個(gè)變更檢測流程中,比如去點(diǎn)擊按鈕,count值將會(huì)變成6(5+1)。

3. 顯示的去執(zhí)行變更檢測

Angular給我們提供了3種方法來觸發(fā)變更檢測。

第一個(gè)是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);

  }

}

第二個(gè)是ApplicationRef.tick(),它告訴Angular來對整個(gè)應(yīng)用程序執(zhí)行變更檢測。

tick() {
 
  try {
    this._views.forEach((view) => view.detectChanges());
    ...
  } catch (e) {
    ...
  }
}

第三是markForCheck(),它不會(huì)觸發(fā)變更檢測。相反,它會(huì)將所有設(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;
  }
}

需要注意的是,手動(dòng)執(zhí)行變更檢測并不是一種“hack”,這是Angular有意的設(shè)計(jì)并且是非常合理的行為(當(dāng)然,是在合理的場景下)。

Angular Async  pipe

async pipe會(huì)訂閱一個(gè) Observable 或 Promise,并返回它發(fā)出的最近一個(gè)值。

讓我們看一個(gè)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)并不會(huì)檢車該組件。

現(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)擊按鈕時(shí),視圖也更新了。原因是當(dāng)新的值被發(fā)射出來時(shí),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ā)生改變。

如果一個(gè)組件僅僅依賴于它的input屬性,并且input屬性是observable,那么這個(gè)組件只有在它的input屬性發(fā)射一個(gè)事件的時(shí)候才會(huì)發(fā)生改變。

Quick tip:對外部暴露你的subject是不值得提倡的,總是使用asObservable()方法來暴露該observable。

onPush和視圖查詢

@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>

也許你會(huì)以為3秒后Angular將會(huì)使用新的內(nèi)容更新tab組件。

畢竟,我們更新來onPush組件的input引用,這將會(huì)觸發(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會(huì)創(chuàng)建一個(gè)叫updateRenderer()的方法,它會(huì)在每個(gè)變更檢測循環(huán)中都對content的值進(jìn)行追蹤。

Angular中的onPush變更檢測策略有哪些

在這種情況下簡單的解決辦法使用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++

在理解了onPush的強(qiáng)大之后,我們來利用它創(chuàng)造一個(gè)更高性能的應(yīng)用。onPush組件越多,Angular需要執(zhí)行的檢查就越少。讓我們看看你一個(gè)真是的例子:

我們又一個(gè)todos組件,它有一個(gè)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í),即使之前的數(shù)據(jù)沒有任何更改,Angular也需要檢查每個(gè)todo。因此第一次單擊后,控制臺(tái)中將顯示三個(gè)日志。

在上面的示例中,只有一個(gè)表達(dá)式需要檢查,但是想象一下如果是一個(gè)有多個(gè)綁定(ngIf,ngClass,表達(dá)式等)的真實(shí)組件,這將會(huì)非常耗性能。

我們白白的執(zhí)行了變更檢測!

更高效的方法是創(chuàng)建一個(gè)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)我們單擊添加按鈕時(shí),控制臺(tái)中只會(huì)看到一個(gè)日志,因?yàn)槠渌膖odo組件的input均未更改,因此不會(huì)去檢查其視圖。

并且,通過創(chuàng)建更小粒度的組件,我們的代碼變得更具可讀性和可重用性。

關(guān)于Angular中的onPush變更檢測策略有哪些就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

新聞名稱:Angular中的onPush變更檢測策略有哪些
標(biāo)題路徑:http://bm7419.com/article6/jdgiog.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、靜態(tài)網(wǎng)站、服務(wù)器托管、動(dòng)態(tài)網(wǎng)站品牌網(wǎng)站設(shè)計(jì)、用戶體驗(yàn)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設(shè)計(jì)