MarkForCheck () ve scanChanges () arasındaki fark nedir


175

Arasındaki fark nedir ChangeDetectorRef.markForCheck()ve ChangeDetectorRef.detectChanges()?

Ben sadece bu iki fonksiyon arasındaki fark hakkında SO hakkında bilgi buldumNgZone.run() , ama değil.

Yalnızca dokümanı referans alan yanıtlar için, lütfen diğerini seçmek için bazı pratik senaryoları gösterin.



@Milad İndirdiğini nereden biliyorsun? Bu siteyi inceleyen birçok insan var.
Hoşçakal StackExchange

2
@FrankerZ, çünkü yazıyordum ve aşağı oyu gördüm ve bir saniye sonra, "Sadece dokümanı referans alan cevaplar için, diğerini seçmemiz için lütfen bazı pratik senaryoları gösterin? aklımda".
Milad

3
Aşağı oy, sizi daha önce gördüğüm dokümanlardan kopyalayıp yapıştırılan orijinal cevabı tamamlamaya teşvik etmekti. Ve işe yaradı! Şimdi cevap çok net ve kabul edilen cevap, teşekkürler
parlamento

3
parlamentoda ne büyük bir plan!
HankCa

Yanıtlar:


234

Dokümanlar'dan:

scanChanges (): void

Değişim dedektörünü ve çocuklarını kontrol eder.

Bu, modelinizdeki (sınıfınız) herhangi bir şeyin değiştiği ancak görünümü yansıtmadığı bir durum varsa, bu değişiklikleri tespit etmek (yerel değişiklikleri tespit etmek) ve görünümü güncellemek için Angular'a bildirmeniz gerekebilir.

Olası senaryolar şunlar olabilir:

1- değişikliği detektörü görünümü ayrılır (bkz ayırmak )

2- Bir güncelleme oldu, ancak Açısal Bölge içinde değildi, bu nedenle Angular bunu bilmiyor.

Üçüncü taraf bir işlev modelinizi güncellediğinde ve bundan sonra görünümü güncellemek istediğinizde olduğu gibi.

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

Bu kod Angular'ın bölgesi (muhtemelen) dışında olduğundan, büyük olasılıkla değişiklikleri algıladığınızdan ve görünümü güncellediğinizden emin olmanız gerekir:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

NOT :

Yukarıda çalışmanın başka yolları da vardır, başka bir deyişle, bu değişikliği Açısal değişim döngüsüne getirmenin başka yolları da vardır.

** Üçüncü taraf işlevini bir bölge içine koyabilirsiniz. Run:

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

** İşlevi bir setTimeout içine sarabilirsiniz:

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

3- Modeli change detection cyclebitirdikten sonra güncellediğiniz durumlar da vardır, bu durumlarda bu korkunç hatayı alırsınız:

"İfade kontrol edildikten sonra değişti";

Bu genellikle (Angular2 dilinden) şu anlama gelir:

Modelinizde, kabul edilen yöntemlerimden (etkinlikler, XHR istekleri, setTimeout ve ...) kaynaklanan bir değişiklik gördüm ve ardından görünümünüzü güncellemek için değişiklik algılamamı çalıştırdım ve bitirdim, ancak sonra başka bir şey vardı kodunuzda modeli tekrar güncelledi ve artık değişiklik algılamamı tekrar çalıştırmak istemiyorum çünkü AngularJS gibi kirli kontroller yok: D ve tek yönlü veri akışı kullanmalıyız!

Kesinlikle bu hatayla karşılaşacaksınız: P.

Bunu düzeltmenin birkaç yolu:

1- Doğru yol : güncellemenin değişiklik algılama döngüsünün içinde olduğundan emin olun (Açısal2 güncellemeleri bir kez gerçekleşen tek yönlü akıştır, bundan sonra modeli güncellemeyin ve kodunuzu daha iyi bir yere / saate taşıyın).

2- Tembel yol : açısal2'yi mutlu etmek için bu güncellemeden sonra detChanges () 'i çalıştırın, bu kesinlikle en iyi yol değildir, ancak olası senaryoların ne olduğunu sorduğunuzda, bunlardan biri budur.

Bu şekilde söylüyorsunuz: Değişiklik tespiti yaptığınızı içtenlikle biliyorum, ama tekrar yapmanızı istiyorum çünkü kontrolü bitirdikten sonra anında bir şeyler güncellemem gerekti.

3- Kodu a içine koyun setTimeout, çünkü setTimeoutbölgeye göre yamalı ve detectChangesbittikten sonra çalışacaktır .


Dokümanlardan

markForCheck() : void

Tüm ChangeDetectionStrategy atalarını kontrol edilecek şekilde işaretler.

Bu çoğunlukla bileşeninizin ChangeDetectionStrategy öğesi OnPush olduğunda gereklidir .

OnPush'ın kendisi, değişiklik algılamasını yalnızca bunlardan biri gerçekleştiğinde çalıştırın:

1- Bileşenin @input'larından biri tamamen yeni bir değerle değiştirildi veya @Input özelliğinin başvurusu tamamen değiştiyse basitçe ifade edildi.

Yani eğer ChangeDetectionStrategy sizin bileşeninin olduğunu OnPush sonra ve sahip:

   var obj = {
     name:'Milad'
   };

Ve sonra günceller / değiştirirsiniz:

  obj.name = "a new name";

Bu obj referansını güncellemeyecektir , bu nedenle değişiklik tespiti çalışmaz, bu nedenle görünüm güncellemeyi / mutasyonu yansıtmaz.

Bu durumda, Angular'a görünümü kontrol etmesini ve güncellemesini manuel olarak söylemeniz gerekir (markForCheck);

Eğer bunu yaptıysanız:

  obj.name = "a new name";

Bunu yapmanız gerekir:

  this.cd.markForCheck();

Aksine, aşağıda bir değişiklik algılamasının çalışmasına neden olur:

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

Hangi önceki obj tamamen yeni bir yerine {};

2- Bir olay, bir tıklama gibi veya böyle bir şey tetiklendi veya alt bileşenlerden herhangi biri bir olay yayınladı.

Gibi etkinlikler:

  • Tıklayın
  • keyup
  • Abonelik etkinlikleri
  • vb.

Kısacası:

  • Kullanım detectChanges()Eğer açısal sonra modelimizi güncelledik 's değişim algılama tükendi veya güncelleme hiç açısal dünyada olmadıysa.

  • Kullanım markForCheck()sen OnPush kullanıyor ve atlayarak eğer ChangeDetectionStrategybazı verileri mutasyona uğratarak veya bir iç modelimizi güncelledik setTimeout ;


6
Bu nesneyi değiştirirseniz, görünüm güncellenmez ve detectChanges'i çalıştırsanız bile, herhangi bir değişiklik olmadığı için çalışmaz - bu doğru değildir. detectChangesgüncelleme görünümü. Bkz bu derinlemesine bir açıklama .
Max Koretskyi

Sonuçta markForCheck ile ilgili olarak, bu da doğru değil. İşte bu sorudan değiştirilmiş örnek , OnPush ve markForCheck ile nesne değişikliklerini algılamıyor. Ancak , OnPush stratejisi yoksa aynı örnek işe yarayacaktır .
Estus Flask

@Maximus, İlk yorumunuzla ilgili olarak, yazınızı okudum, iyi olduğu için teşekkürler. Ancak açıklamanızda, stratejinin OnPush olup this.cdMode === ChangeDetectorStatus.Checkedolmadığını, görünümü güncellemeyecekse, markForCheck'i kullanacağınızı söylüyorsunuz.
Milad

Ve dalgıçla bağlantılarınızla ilgili olarak, her iki örnek de benim için iyi çalışıyor, ne demek istediğinizi bilmiyorum
Milad

@Milad, bu yorumlar @estus'tan geldi :). Benimki detectChanges. Ve cdModeAçısal'da hiç yok 4.x.x. Makalemde bunun hakkında yazıyorum. Beğendiğine sevindim. Unutmayın
ortada

99

İkisi arasındaki en büyük fark, detectChanges()değişiklik algılamayı markForCheck()tetiklemezken değişiklik algılamayı tetiklememesidir.

detectChanges

Bu, tetiklediğiniz bileşenden başlayarak bileşen ağacı için değişiklik algılamayı çalıştırmak için kullanılır detectChanges(). Bu nedenle, mevcut bileşen ve tüm alt öğeleri için değişiklik algılaması çalıştırılacaktır. Açısal, kök bileşen ağacına başvuruları tutar ApplicationRefve herhangi bir eşzamansız işlem gerçekleştiğinde, bir sarmalayıcı yöntemiyle bu kök bileşenindeki değişiklik algılamasını tetikler 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()); <------------------

viewİşte kök bileşen görünümü. Birden çok bileşenin önyüklemenin etkileri nelerdir? Bölümünde açıkladığım gibi birçok kök bileşen olabilir .

@milad, potansiyel olarak değişiklik algılamayı manuel olarak tetiklemenizin gerekebileceğini açıkladı.

markForCheck

Dediğim gibi, bu adam değişiklik algılamayı hiç tetiklemiyor. Sadece geçerli bileşenden kök bileşene doğru yükselir ve görünüm durumunu olarak günceller ChecksEnabled. Kaynak kod:

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

Bileşen için gerçek değişiklik algılaması zamanlanmamıştır ancak gelecekte (geçerli veya bir sonraki CD döngüsünün parçası olarak) ne zaman gerçekleşecekse, değişiklik algılayıcılarını sökmüş olsalar bile ana bileşen görünümleri kontrol edilir. Değişiklik algılayıcıları, değişiklik algılama stratejisini kullanarak cd.detach()veya belirleyerek ayrılabilir OnPush. Tüm yerel olay işleyicileri tüm üst bileşen görünümlerini kontrol için işaretler.

Bu yaklaşım genellikle ngDoCheckyaşam döngüsü kancasında kullanılır. Daha okuyabilirsiniz düşünürseniz ngDoChecksizin bileşeni kontrol ediliyor araçlar - bu makaleyi okuyun .

Daha fazla bilgi için Angular'daki değişiklik algılama hakkında bilmeniz gereken her şeye bakın .


1
DetectChanges neden bileşen ve alt öğeleri üzerinde çalışırken markForCheck bileşen ve ataları üzerinde çalışır?
pablo

@pablo, tasarım gereği.
Gerekçeye

@ AngularInDepth.com çok yoğun işleme tabi tutulursa, changeetection kullanıcı arayüzünü engeller mi?
alt255

1
@jerry, önerilen yaklaşım, aboneliği dahili olarak izleyen ve her yeni değer tetikleyicisinde asenkron boru kullanmaktır markForCheck. Eğer asenkron boru kullanmıyorsanız, muhtemelen kullanmanız gereken budur. Ancak, değişiklik algılamanın başlaması için mağaza güncellemesinin bazı zaman uyumsuz olayların bir sonucu olarak gerçekleşmesi gerektiğini unutmayın. Her zaman böyle olur. Ancak istisnalar var blog.angularindepth.com/…
Max Koretskyi

1
@MaxKoretskyiakaWizard cevap için teşekkürler. Evet, mağaza güncellemesi çoğunlukla bir getirme veya daha önce ayarlanmış bir ayardır. ve getirdikten sonra .. ama her zaman kullanamayız, async pipeçünkü abonelik içinde genellikle yapmak için birkaç şeyimiz var call setFromValues do some comparison.. ve eğer asynckendisi çağırırsak markForCheck, sorunu kendimiz çağırırsak? ama yine ngOnInitde farklı veriler elde etmede genellikle 2-3 ya da bazen daha fazla seçicimiz var ... ve markForCheckhepsine sesleniyoruz.
jerry

0

cd.detectChanges() değişiklik algılamayı geçerli bileşenden derhal aşağıya doğru çalıştıracaktır.

cd.markForCheck()değişiklik algılamayı çalıştırmaz, ancak atalarını değişiklik algılamayı çalıştırması gerektiği olarak işaretler. Bir dahaki sefere değişiklik tespiti her yerde çalışır, işaretlenen bileşenler için de çalışır.

  • Değişim tespit sayısını kaç kez azaltmak istiyorsanız kullanım denir cd.markForCheck(). Genellikle, değişiklikler birden çok bileşeni etkiler ve bir yerlerde değişiklik algılaması çağrılır. Aslında şunu söylüyorsunuz: bu bileşenin bu durumda da güncellendiğinden emin olalım . (Görünüm yazdığım her projede hemen güncellenir, ancak her birim testinde güncellenmez).
  • Şu anda değişiklik algılamayı çalıştırmadığından emin cd.detectChanges()değilseniz kullanın . bu durumda hata verecektir. Bu muhtemelen, Angular'ın değişiklik algılamasının etrafında tasarlandığı varsayımlarına karşı çalışan bir ata bileşeninin durumunu düzenlemeye çalıştığınız anlamına gelir. cd.markForCheck()detectChanges()
  • Başka bir işlemden önce görünümün senkronize olarak güncellenmesi kritik önem taşıyorsa kullanın detectChanges(). markForCheck()görünümünüzü zamanında güncellemeyebilir. Örneğin bir şeyi test etmek görünümünüzü etkiler, örneğin fixture.detectChanges()uygulamanın kendisinde gerekli olmadığında manuel olarak arama yapmanızı gerektirebilir .
  • Torundan daha fazla ata sahip bir bileşendeki durumu değiştiriyorsanız, bileşenin atalarında detectChanges()gereksiz yere değişiklik algılaması yapmadığınız için kullanarak performans artışı alabilirsiniz .
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.