Apa perbedaan antara markForCheck () dan detectChanges ()

174

Apa perbedaan antara ChangeDetectorRef.markForCheck()dan ChangeDetectorRef.detectChanges()?

Saya hanya menemukan informasi tentang perbedaan antara SONgZone.run() , tetapi tidak antara dua fungsi ini.

Untuk jawaban dengan hanya referensi ke dokumen, harap ilustrasikan beberapa skenario praktis untuk memilih satu dari yang lain.

parlemen
sumber
@Milad Bagaimana Anda tahu dia menurunkannya? Ada banyak orang yang membaca dengan teliti situs ini.
Selamat tinggal StackExchange
2
@ FrankerZ, karena saya sedang menulis dan saya melihat downvote dan sedetik kemudian pertanyaan itu diperbarui mengatakan bahwa "Untuk jawaban dengan hanya referensi ke dokumen, tolong ilustrasikan beberapa skenario praktis untuk memilih satu dari yang lain? Itu akan membantu memperjelasnya dalam pikiranku ".
Milad
3
Downvote adalah untuk memberi Anda insentif untuk menyelesaikan jawaban asli yang baru saja disalin & ditempelkan dari dokumen yang sudah saya lihat. Dan itu berhasil! Sekarang jawabannya memiliki banyak kejelasan dan merupakan jawaban yang diterima, terima kasih
parlemen
3
Apa rencana licik @parlemen!
HankCa

Jawaban:

234

Dari dokumen:

detectChanges (): void

Periksa detektor perubahan dan anak-anaknya.

Itu berarti, jika ada kasus di mana sesuatu di dalam model Anda (kelas Anda) telah berubah tetapi itu tidak mencerminkan tampilan, Anda mungkin perlu memberi tahu Angular untuk mendeteksi perubahan tersebut (mendeteksi perubahan lokal) dan memperbarui tampilan.

Skenario yang mungkin:

1- Detektor perubahan dilepaskan dari pandangan (lihat melepaskan )

2- Pembaruan telah terjadi tetapi belum ada di dalam Zona Angular, oleh karena itu, Angular tidak mengetahuinya.

Seperti ketika fungsi pihak ketiga memperbarui model Anda dan Anda ingin memperbarui tampilan setelah itu.

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

Karena kode ini berada di luar zona Angular (mungkin), kemungkinan besar Anda perlu memastikan untuk mendeteksi perubahan dan memperbarui tampilan, dengan demikian:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

CATATAN :

Ada cara lain untuk membuat pekerjaan di atas, dengan kata lain, ada cara lain untuk membawa perubahan itu dalam siklus perubahan sudut.

** Anda bisa membungkus fungsi pihak ketiga itu di dalam zone.run:

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** Anda bisa membungkus fungsi di dalam setTimeout:

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3 - Ada juga kasus di mana Anda memperbarui model setelah change detection cycleselesai, di mana dalam kasus tersebut Anda mendapatkan kesalahan yang ditakuti ini:

"Ekspresi telah berubah setelah diperiksa";

Ini umumnya berarti (dari bahasa Angular2):

Saya melihat perubahan pada model Anda yang disebabkan oleh salah satu cara saya yang diterima (peristiwa, permintaan XHR, setTimeout, dan ...) dan kemudian saya menjalankan deteksi perubahan untuk memperbarui tampilan Anda dan saya menyelesaikannya, tetapi kemudian ada lagi berfungsi dalam kode Anda yang memperbarui model lagi dan saya tidak ingin menjalankan deteksi perubahan saya lagi karena tidak ada pemeriksaan kotor seperti AngularJS lagi: D dan kita harus menggunakan aliran data satu arah!

Anda pasti akan menemukan kesalahan ini: P.

Beberapa cara untuk memperbaikinya:

1- Cara yang benar : pastikan pembaruan ada di dalam siklus deteksi perubahan (Pembaruan Angular2 adalah salah satu cara yang terjadi sekali, jangan perbarui model setelah itu dan pindahkan kode Anda ke tempat / waktu yang lebih baik).

2- Cara malas : jalankan detectChanges () setelah pembaruan itu untuk membuat angular2 bahagia, ini jelas bukan cara terbaik, tetapi ketika Anda bertanya skenario apa yang mungkin terjadi, ini adalah salah satunya.

Dengan cara ini Anda mengatakan: Saya sungguh-sungguh tahu Anda menjalankan deteksi perubahan, tetapi saya ingin Anda melakukannya lagi karena saya harus memperbarui sesuatu dengan cepat setelah Anda selesai memeriksa.

3 - Masukkan kode di dalam setTimeout, karena setTimeoutditambal oleh zona dan akan berjalan detectChangessetelah selesai.


Dari dokumen

markForCheck() : void

Tandai semua leluhur ChangeDetectionStrategy yang akan diperiksa.

Ini sebagian besar diperlukan ketika ChangeDetectionStrategy komponen Anda adalah OnPush .

OnPush sendiri berarti, hanya menjalankan deteksi perubahan jika salah satu dari ini terjadi:

1- Salah satu input @ komponen telah sepenuhnya diganti dengan nilai baru, atau cukup cantumkan, jika referensi properti @ Input telah berubah sama sekali.

Jadi jika ChangeDetectionStrategy dari komponen Anda adalah OnPush dan Anda memiliki:

   var obj = {
     name:'Milad'
   };

Dan kemudian Anda memperbarui / bermutasi seperti:

  obj.name = "a new name";

Ini tidak akan memperbarui obj referensi, maka deteksi perubahan tidak akan lari, karena pandangan tidak mencerminkan update / mutasi.

Dalam hal ini Anda harus memberi tahu Angular secara manual untuk memeriksa dan memperbarui tampilan (markForCheck);

Jadi, jika Anda melakukan ini:

  obj.name = "a new name";

Anda perlu melakukan ini:

  this.cd.markForCheck();

Sebaliknya, di bawah ini akan menyebabkan deteksi perubahan berjalan:

    obj = {
      name:"a new name"
    };

Yang sepenuhnya menggantikan objek sebelumnya dengan yang baru {};

2- Suatu peristiwa telah dipecat, seperti klik atau sesuatu seperti itu atau komponen anak mana saja yang telah memancarkan suatu peristiwa.

Acara seperti:

  • Klik
  • Keyup
  • Acara berlangganan
  • dll.

Singkatnya:

  • Gunakan detectChanges()saat Anda memperbarui model setelah sudut menjalankan deteksi perubahan, atau jika pembaruan belum ada di dunia sudut sama sekali.

  • Gunakan markForCheck()jika Anda menggunakan OnPush dan Anda mem-bypass ChangeDetectionStrategydengan mematikan beberapa data atau Anda telah memperbarui model di dalam setTimeout ;

Milad
sumber
6
Jadi jika Anda menonaktifkan objek itu, tampilan tidak akan diperbarui, dan bahkan jika Anda menjalankan detectChanges, itu tidak akan berhasil karena belum ada perubahan - ini tidak benar. detectChangestampilan pembaruan. Lihat penjelasan mendalam ini .
Max Koretskyi
Mengenai markForCheck dalam kesimpulan, itu tidak akurat juga. Berikut contoh yang dimodifikasi dari pertanyaan ini , tidak mendeteksi perubahan objek dengan OnPush dan markForCheck. Tetapi contoh yang sama akan bekerja jika tidak ada strategi OnPush.
Estus Flask
@ Maximus, Mengenai komentar pertama Anda, saya membaca posting Anda, terima kasih untuk itu bagus. Tetapi dalam penjelasan Anda, Anda mengatakan jika strateginya adalah OnPush, berarti jika this.cdMode === ChangeDetectorStatus.Checkeditu tidak akan memperbarui tampilan, itu sebabnya Anda akan menggunakan markForCheck.
Milad
Mengenai tautan Anda ke plunker, kedua contoh berfungsi dengan baik untuk saya, saya tidak tahu apa maksud Anda
Milad
@Milad, komentar-komentar itu datang dari @estus :). Punyaku tentang detectChanges. Dan tidak ada cdModedalam Angular 4.x.x. Saya menulis tentang itu di artikel saya. Senang kamu menyukainya. Jangan lupa, Anda dapat merekomendasikannya pada media atau ikuti saya :)
Max Koretskyi
99

Perbedaan terbesar antara keduanya adalah yang detectChanges()sebenarnya memicu deteksi perubahan, sementara markForCheck()tidak memicu deteksi perubahan.

mendeteksi perubahan

Yang ini digunakan untuk menjalankan deteksi perubahan untuk pohon komponen yang dimulai dengan komponen yang Anda picu detectChanges(). Jadi deteksi perubahan akan berjalan untuk komponen saat ini dan semua anak-anaknya. Sudut memegang referensi ke pohon komponen root di ApplicationRefdan ketika operasi async terjadi itu memicu deteksi perubahan pada komponen root ini melalui metode pembungkus tick():

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

viewdi sini adalah tampilan komponen root. Mungkin ada banyak komponen root seperti yang saya jelaskan di Apa implikasi dari beberapa komponen bootstrap .

@milad menjelaskan alasan mengapa Anda berpotensi perlu memicu deteksi perubahan secara manual.

tandaiCheck

Seperti yang saya katakan, orang ini sama sekali tidak memicu deteksi perubahan. Itu hanya naik dari komponen saat ini ke komponen root dan memperbarui status tampilan mereka ChecksEnabled. Ini kode sumbernya:

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;
  }
}

Deteksi perubahan aktual untuk komponen tidak dijadwalkan tetapi ketika akan terjadi di masa mendatang (baik sebagai bagian dari siklus CD saat ini atau berikutnya) tampilan komponen induk akan diperiksa bahkan jika mereka telah melepaskan detektor perubahan. Detektor perubahan dapat dilepaskan dengan menggunakan cd.detach()atau dengan menentukan OnPushstrategi deteksi perubahan. Semua penangan acara asli menandai semua tampilan komponen induk untuk diperiksa.

Pendekatan ini sering digunakan dalam ngDoCheckkait siklus hidup. Anda dapat membaca lebih lanjut di bagian Jika Anda merasa ngDoCheckkomponen Anda sedang diperiksa - baca artikel ini .

Lihat juga Semua yang perlu Anda ketahui tentang deteksi perubahan di Angular untuk detail lebih lanjut.

Max Koretskyi
sumber
1
Mengapa detectChanges bekerja pada komponen dan anak-anaknya sementara menandai ForCheck pada komponen dan leluhurnya?
pablo
@ Pedro, itu dengan desain. Saya tidak begitu akrab dengan alasannya
Max Koretskyi
@ AngularInDepth.com apakah perubahaneksi memblokir UI jika ada pemrosesan yang sangat intensif?
alt255
1
@ Jerry, pendekatan yang disarankan adalah menggunakan pipa async, yang secara internal melacak langganan dan pada setiap pemicu nilai baru markForCheck. Jadi jika Anda tidak menggunakan pipa async, mungkin itulah yang harus Anda gunakan. Namun, perlu diingat, bahwa pembaruan toko harus terjadi sebagai akibat dari beberapa peristiwa async untuk memulai deteksi perubahan. Itulah yang selalu terjadi. Tetapi ada pengecualian blog.angularindepth.com/…
Max Koretskyi
1
@ MaxKoretskyiakaWizard terima kasih atas jawabannya. Ya, pembaruan toko sebagian besar merupakan hasil pengambilan atau pengaturan adalah Mengambil sebelumnya. dan setelah mengambil .. tetapi kita tidak dapat selalu menggunakan async pipekarena di dalam berlangganan kita biasanya memiliki beberapa hal yang harus dilakukan seperti call setFromValues do some comparison.. dan jika asyncitu sendiri memanggil markForCheckapa masalahnya jika kita menyebutnya sendiri? tetapi sekali lagi kami biasanya memiliki 2-3 atau kadang-kadang lebih banyak penyeleksi dalam ngOnInitmendapatkan data yang berbeda ... dan kami memanggil markForChecksemuanya .. apakah itu OK?
jerry
0

cd.detectChanges() akan menjalankan deteksi perubahan segera dari komponen saat ini turun melalui turunannya.

cd.markForCheck()tidak akan menjalankan deteksi perubahan, tetapi menandai leluhurnya sebagai perlu menjalankan deteksi perubahan. Deteksi perubahan waktu berikutnya berjalan di mana saja, itu akan berjalan juga untuk komponen-komponen yang ditandai.

  • Jika Anda ingin mengurangi berapa kali perubahan deteksi disebut gunakan cd.markForCheck(). Seringkali, perubahan memengaruhi banyak komponen dan di suatu tempat deteksi pendeteksian akan dipanggil. Anda pada dasarnya mengatakan: mari kita pastikan komponen ini juga diperbarui ketika itu terjadi. (Tampilan segera diperbarui di setiap proyek yang saya tulis, tetapi tidak di setiap unit test).
  • Jika Anda tidak dapat memastikan bahwa saat inicd.detectChanges() tidak menjalankan deteksi perubahan, gunakan . akan kesalahan dalam hal itu. Ini mungkin berarti Anda mencoba mengedit keadaan komponen leluhur, yang bekerja melawan asumsi bahwa deteksi perubahan Angular dirancang. cd.markForCheck()detectChanges()
  • Jika penting agar tampilan diperbarui secara sinkron sebelum melakukan tindakan lain, gunakan detectChanges(). markForCheck()mungkin tidak benar-benar memperbarui tampilan Anda tepat waktu. Unit menguji sesuatu yang memengaruhi tampilan Anda, misalnya, mungkin mengharuskan Anda menelepon secara manual fixture.detectChanges()saat itu tidak diperlukan dalam aplikasi itu sendiri.
  • Jika Anda mengubah status dalam komponen dengan lebih banyak leluhur daripada keturunan, Anda mungkin mendapatkan peningkatan kinerja dengan menggunakan detectChanges()karena Anda tidak perlu menjalankan deteksi perubahan pada leluhur komponen.
Kevin Beal
sumber