Fahami dengan cepat strategi pengesanan perubahan onPush dalam Angular

青灯夜游
Lepaskan: 2021-09-09 19:44:36
ke hadapan
2211 orang telah melayarinya

Artikel ini akan memberi anda pemahaman yang mendalam tentang strategi pengesanan perubahan onPush dalam Angular saya harap ia akan membantu anda!

Fahami dengan cepat strategi pengesanan perubahan onPush dalam Angular

Strategi pengesanan perubahan lalai

Secara lalai, Angular menggunakan strategi ChangeDetectionStrategy.Default untuk pengesanan perubahan.

Strategi lalai tidak membuat sebarang andaian tentang aplikasi terlebih dahulu Oleh itu, apabila peristiwa pengguna, pemasa, XHR, janji dan peristiwa lain menyebabkan data dalam aplikasi berubah, semua komponen akan Melakukan pengesanan perubahan.

Ini bermakna bahawa sebarang peristiwa daripada peristiwa klik kepada data yang diterima daripada panggilan ajax akan mencetuskan pengesanan perubahan.

Kita boleh melihatnya dengan mudah dengan mentakrifkan pengambil dalam komponen dan menggunakannya dalam templat:

@Component({
  template: `
    <h1>Hello {{name}}!</h1>
    {{runChangeDetection}}
  `
})
export class HelloComponent {
  @Input() name: string;

  get runChangeDetection() {
    console.log(&#39;Checking the view&#39;);
    return true;
  }
}
Salin selepas log masuk
@Component({
  template: `
    <hello></hello>
    <button (click)="onClick()">Trigger change detection</button>
  `
})
export class AppComponent  {
  onClick() {}
}
Salin selepas log masuk

Selepas melaksanakan kod di atas, bila-bila masa kita Apabila butang diklik . Angular akan melaksanakan gelung pengesanan perubahan, dan dalam konsol kita boleh melihat dua baris log "Menyemak paparan".

Teknik ini dipanggil pemeriksaan kotor. Untuk mengetahui sama ada paparan perlu dikemas kini, Angular perlu mengakses nilai baharu dan membandingkannya dengan nilai lama untuk menentukan sama ada paparan perlu dikemas kini.

Sekarang bayangkan jika terdapat aplikasi besar dengan beribu-ribu ungkapan, dan Angular menyemak setiap ungkapan, kita mungkin menghadapi masalah prestasi.

Jadi adakah cara untuk kami memberitahu Angular secara proaktif bila hendak menyemak komponen kami?

Strategi pengesanan perubahan OnPush

Kami boleh menetapkan ChangeDetectionStrategy komponen kepada ChangeDetectionStrategy.OnPush.

Ini akan memberitahu Angular bahawa komponen hanya bergantung pada @inputs()nya dan hanya perlu diperiksa dalam situasi berikut:

1 Rujukan Input berubah

Dengan menetapkan onPush strategi pengesanan perubahan, kami membuat kontrak dengan Angular untuk menguatkuasakan penggunaan objek tidak boleh ubah (atau boleh diperhatikan seperti yang akan kami perkenalkan kemudian).

Kelebihan menggunakan objek tidak berubah dalam konteks pengesanan perubahan ialah Sudut boleh menentukan sama ada paparan perlu disemak dengan menyemak sama ada rujukan telah berubah. Ini akan menjadi lebih mudah daripada pemeriksaan mendalam.

Mari cuba ubah suai objek dan lihat hasilnya.

@Component({
  selector: &#39;tooltip&#39;,
  template: `
    <h1>{{config.position}}</h1>
    {{runChangeDetection}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipComponent  {

  @Input() config;

  get runChangeDetection() {
    console.log(&#39;Checking the view&#39;);
    return true;
  }
}
Salin selepas log masuk
@Component({
  template: `
    <tooltip [config]="config"></tooltip>
  `
})
export class AppComponent  {
  config = {
    position: &#39;top&#39;
  };

  onClick() {
    this.config.position = &#39;bottom&#39;;
  }
}
Salin selepas log masuk

Anda tidak dapat melihat sebarang log apabila anda mengklik butang pada masa ini Ini kerana Angular membandingkan rujukan nilai lama dan nilai baharu, serupa dengan:

.
/** Returns false in our case */
if( oldValue !== newValue ) { 
  runChangeDetection();
}
Salin selepas log masuk

Perlu dinyatakan bahawa nombor, boolean, rentetan, null dan undefined adalah semua jenis primitif. Semua jenis primitif diluluskan mengikut nilai Objek, tatasusunan dan fungsi juga diluluskan mengikut nilai, tetapi nilai tersebut adalah salinan alamat rujukan.

Jadi untuk mencetuskan pengesanan perubahan pada komponen ini, kita perlu menukar rujukan kepada objek ini.

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

  onClick() {
    this.config = {
      position: &#39;bottom&#39;
    }
  }
}
Salin selepas log masuk
Selepas menukar rujukan objek, kita akan melihat bahawa paparan telah disemak dan nilai baharu dipaparkan.

2. Peristiwa yang berasal daripada komponen atau subkomponennya

Apabila peristiwa dicetuskan dalam komponen atau subkomponennya, keadaan dalaman komponen akan dikemas kini. Contohnya:

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

  add() {
    this.count++;
  }

}
Salin selepas log masuk
Apabila kita mengklik pada butang, Angular melaksanakan gelung pengesanan perubahan dan mengemas kini paparan.

Anda mungkin berfikir bahawa, seperti yang kami katakan pada mulanya, setiap API tak segerak akan mencetuskan pengesanan perubahan, tetapi ini tidak berlaku.

Anda akan mendapati bahawa peraturan ini hanya terpakai pada peristiwa DOM API berikut tidak akan mencetuskan pengesanan perubahan:

@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(&#39;https://count.com&#39;).subscribe(res => {
      this.count = res;
    });
  }

  add() {
    this.count++;
  }
Salin selepas log masuk
Perhatikan bahawa anda masih mengemas kini atribut, jadi selepas perubahan seterusnya Semasa. proses pengesanan, contohnya, jika anda mengklik butang, nilai kiraan akan menjadi 6 (5 1).

3. Lakukan pengesanan perubahan secara eksplisit

Angular memberikan kami 3 kaedah untuk mencetuskan pengesanan perubahan.

Yang pertama ialah

untuk memberitahu Angular untuk melakukan pengesanan perubahan pada komponen ini dan subkomponennya. detectChanges()

@Component({
  selector: &#39;counter&#39;,
  template: `{{count}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent { 
  count = 0;

  constructor(private cdr: ChangeDetectorRef) {

    setTimeout(() => {
      this.count = 5;
      this.cdr.detectChanges();
    }, 1000);

  }

}
Salin selepas log masuk
Yang kedua ialah

, yang memberitahu Angular untuk melakukan pengesanan perubahan pada keseluruhan aplikasi. ApplicationRef.tick()

tick() {
 
  try {
    this._views.forEach((view) => view.detectChanges());
    ...
  } catch (e) {
    ...
  }
}
Salin selepas log masuk
Yang ketiga ialah

, yang tidak mencetuskan pengesanan perubahan. Sebaliknya, ia akan mengesan semua teg nenek moyang dengan set onPush dalam kitaran pengesanan perubahan semasa atau seterusnya. markForCheck()

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;
  }
}
Salin selepas log masuk
Perlu diingat bahawa melakukan pengesanan perubahan secara manual bukanlah "penggodaman". Ini adalah reka bentuk Angular yang disengajakan dan merupakan tingkah laku yang sangat munasabah (sudah tentu, di bawah senario yang munasabah).

Paip Async Sudut

Paip akan melanggan Observable atau Promise dan mengembalikan nilai terkini yang dikeluarkannya. async

Mari kita lihat komponen onPush yang

boleh diperhatikan. input()

@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);
  }
}
Salin selepas log masuk
@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;
    });
  }

}
Salin selepas log masuk
Apabila kami mengklik butang kami tidak dapat melihat kemas kini paparan. Ini kerana tiada situasi yang dinyatakan di atas telah berlaku, jadi Angular tidak akan menyemak komponen dalam kitaran pengesanan perubahan semasa.

现在,让我们加上async pipe试试。

@Component({
  template: `
    <div *ngFor="let item of items | async">{{item.title}}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
  @Input() items;
}
Salin selepas log masuk

现在可以看到当我们点击按钮时,视图也更新了。原因是当新的值被发射出来时,async pipe将该组件标记为发生了更改需要检查。我们可以在源码中看到:

private _updateLatestValue(async: any, value: Object): void {
  if (async === this._obj) {
    this._latestValue = value;
    this._ref.markForCheck();
  }
}
Salin selepas log masuk

Angular为我们调用markForCheck(),所以我们能看到视图更新了即使input的引用没有发生改变。

如果一个组件仅仅依赖于它的input属性,并且input属性是observable,那么这个组件只有在它的input属性发射一个事件的时候才会发生改变。

Quick tip:对外部暴露你的subject是不值得提倡的,总是使用asObservable()方法来暴露该observable。

onPush和视图查询

@Component({
  selector: &#39;app-tabs&#39;,
  template: `<ng-content></ng-content>`
})
export class TabsComponent implements OnInit {
  @ContentChild(TabComponent) tab: TabComponent;

  ngAfterContentInit() {
    setTimeout(() => {
      this.tab.content = &#39;Content&#39;; 
    }, 3000);
  }
}
Salin selepas log masuk
@Component({
  selector: &#39;app-tab&#39;,
  template: `{{content}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
  @Input() content;
}
Salin selepas log masuk
<app-tabs>
  <app-tab></app-tab>
</app-tabs>
Salin selepas log masuk

也许你会以为3秒后Angular将会使用新的内容更新tab组件。

毕竟,我们更新来onPush组件的input引用,这将会触发变更检测不是吗?

然而,在这种情况下,它并不生效。Angular不知道我们正在更新tab组件的input属性,在模板中定义input()是让Angular知道应在变更检测循环中检查此属性的唯一途径。

例如:

<app-tabs>
  <app-tab [content]="content"></app-tab>
</app-tabs>
Salin selepas log masuk

因为当我们明确的在模板中定义了input(),Angular会创建一个叫updateRenderer()的方法,它会在每个变更检测循环中都对content的值进行追踪。

Fahami dengan cepat strategi pengesanan perubahan onPush dalam Angular

在这种情况下简单的解决办法使用setter然后调用markForCheck()

@Component({
  selector: &#39;app-tab&#39;,
  template: `
    {{_content}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
  _content;

  @Input() set content(value) {
    this._content = value;
    this.cdr.markForCheck();
  }

  constructor(private cdr: ChangeDetectorRef) {}

}
Salin selepas log masuk

=== onPush++

在理解了onPush的强大之后,我们来利用它创造一个更高性能的应用。onPush组件越多,Angular需要执行的检查就越少。让我们看看你一个真是的例子:

我们又一个todos组件,它有一个todos作为input()。

@Component({
  selector: &#39;app-todos&#39;,
  template: `
     <div *ngFor="let todo of todos">
       {{todo.title}} - {{runChangeDetection}}
     </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
  @Input() todos;

  get runChangeDetection() {
    console.log(&#39;TodosComponent - Checking the view&#39;);
    return true;
  }

}
Salin selepas log masuk
@Component({
  template: `
    <button (click)="add()">Add</button>
    <app-todos [todos]="todos"></app-todos>
  `
})
export class AppComponent {
  todos = [{ title: &#39;One&#39; }, { title: &#39;Two&#39; }];

  add() {
    this.todos = [...this.todos, { title: &#39;Three&#39; }];
  }
}
Salin selepas log masuk

上述方法的缺点是,当我们单击添加按钮时,即使之前的数据没有任何更改,Angular也需要检查每个todo。因此第一次单击后,控制台中将显示三个日志。

在上面的示例中,只有一个表达式需要检查,但是想象一下如果是一个有多个绑定(ngIf,ngClass,表达式等)的真实组件,这将会非常耗性能。

我们白白的执行了变更检测!

更高效的方法是创建一个todo组件并将其变更检测策略定义为onPush。例如:

@Component({
  selector: &#39;app-todos&#39;,
  template: `
    <app-todo [todo]="todo" *ngFor="let todo of todos"></app-todo>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
  @Input() todos;
}

@Component({
  selector: &#39;app-todo&#39;,
  template: `{{todo.title}} {{runChangeDetection}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {
  @Input() todo;

  get runChangeDetection() {
    console.log(&#39;TodoComponent - Checking the view&#39;);
    return true;
  }

}
Salin selepas log masuk

现在,当我们单击添加按钮时,控制台中只会看到一个日志,因为其他的todo组件的input均未更改,因此不会去检查其视图。

并且,通过创建更小粒度的组件,我们的代码变得更具可读性和可重用性。

原文链接: https://netbasal.com/a-comprehensive-guide-to-angular-onpush-change-detection-strategy-5bac493074a4

原文作者:Netanel Basal

译者:淼淼

更多编程相关知识,请访问:编程视频!!

Atas ialah kandungan terperinci Fahami dengan cepat strategi pengesanan perubahan onPush dalam Angular. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:juejin.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!