Jaka jest różnica między markForCheck () i DiscoverChanges ()

174

Jaka jest różnica między ChangeDetectorRef.markForCheck()i ChangeDetectorRef.detectChanges()?

Znalazłem tylko informacje na temat SO co do różnicy między NgZone.run(), ale nie między tymi dwoma funkcjami.

Aby uzyskać odpowiedzi zawierające tylko odniesienie do dokumentu, zilustruj kilka praktycznych scenariuszy, aby wybrać jeden z nich.

parlament
źródło
@Milad Skąd wiesz, że przegłosował to? Wiele osób przegląda tę stronę.
Goodbye StackExchange
2
@FrankerZ, ponieważ pisałem i widziałem głos przeciwny, a sekundę później zaktualizowano pytanie, mówiąc: „W przypadku odpowiedzi zawierających tylko odniesienie do dokumentu, zilustruj kilka praktycznych scenariuszy, aby wybrać jeden zamiast drugiego? To pomoże to wyjaśnić w moim umyśle".
Milad
3
Głos w dół miał zachęcić cię do uzupełnienia oryginalnej odpowiedzi, która została właśnie skopiowana i wklejona z dokumentów, które już widziałem. I zadziałało! Teraz odpowiedź jest bardzo jasna i jest odpowiedzią zaakceptowaną, dzięki
parlament
3
co za przebiegły plan @parliament!
HankCa

Odpowiedzi:

234

Z dokumentów:

DetectChanges (): void

Sprawdza detektor zmian i jego elementy podrzędne.

Oznacza to, że jeśli jest przypadek, w którym coś w twoim modelu (twojej klasie) uległo zmianie, ale nie odzwierciedlało widoku, może być konieczne powiadomienie Angulara, aby wykrył te zmiany (wykrył zmiany lokalne) i zaktualizował widok.

Możliwe scenariusze to:

1- Detektor zmian jest odłączony od widoku (patrz odłączanie )

2- Nastąpiła aktualizacja, ale nie było jej w Angular Zone, dlatego Angular o tym nie wie.

Tak jak wtedy, gdy funkcja innej firmy zaktualizowała Twój model i chcesz zaktualizować widok po tym.

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

Ponieważ ten kod znajduje się poza strefą Angular (prawdopodobnie), najprawdopodobniej musisz upewnić się, że wykryłeś zmiany i zaktualizowałeś widok, w ten sposób:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

UWAGA :

Istnieją inne sposoby, aby powyższe zadziałało, innymi słowy, istnieją inne sposoby wprowadzenia tej zmiany w cyklu zmian Angulara.

** Możesz umieścić tę funkcję strony trzeciej w strefie zone.run:

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

** Możesz zawinąć funkcję wewnątrz setTimeout:

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

3 - Istnieją również przypadki, w których aktualizujesz model po change detection cyclezakończeniu, w których w takich przypadkach pojawia się ten przerażający błąd:

„Wyrażenie zmieniło się po sprawdzeniu”;

To generalnie oznacza (z języka Angular2):

Widziałem zmianę w Twoim modelu, która była spowodowana jednym z moich zaakceptowanych sposobów (zdarzenia, żądania XHR, setTimeout i ...), a następnie uruchomiłem wykrywanie zmian, aby zaktualizować widok i skończyłem, ale potem był inny funkcja w twoim kodzie, która ponownie zaktualizowała model i nie chcę ponownie uruchamiać wykrywania zmian, ponieważ nie ma już brudnego sprawdzania, takiego jak AngularJS: D i powinniśmy używać jednokierunkowego przepływu danych!

Na pewno napotkasz ten błąd: P.

Kilka sposobów, aby to naprawić:

1- Właściwy sposób : upewnij się, że aktualizacja znajduje się w cyklu wykrywania zmian (aktualizacje Angular2 to jednokierunkowy przepływ, który ma miejsce raz, nie aktualizuj później modelu i przenieś swój kod w lepsze miejsce / czas).

2- Leniwy sposób : po tej aktualizacji uruchom funkcję detectionChanges (), aby angular2 był szczęśliwy, zdecydowanie nie jest to najlepszy sposób, ale jeśli zapytałeś, jakie są możliwe scenariusze, to jest jeden z nich.

W ten sposób mówisz: Szczerze wiem, że uruchomiłeś wykrywanie zmian, ale chcę, abyś zrobił to ponownie, ponieważ musiałem aktualizować coś w locie po zakończeniu sprawdzania.

3- Umieść kod wewnątrz a setTimeout, ponieważ setTimeoutjest załatany przez strefę i będzie działać detectChangespo zakończeniu.


Z dokumentów

markForCheck() : void

Zaznacza wszystkie elementy nadrzędne ChangeDetectionStrategy jako do sprawdzenia.

Jest to głównie potrzebne, gdy ChangeDetectionStrategy twojego komponentu jest OnPush .

Sam OnPush oznacza, że ​​uruchamiaj wykrywanie zmian tylko wtedy, gdy wydarzy się którykolwiek z poniższych:

1- Jedno z @inputs komponentu zostało całkowicie zastąpione nową wartością lub po prostu mówiąc, jeśli odniesienie do właściwości @Input uległo całkowitej zmianie.

Więc jeśli ChangeDetectionStrategy twojego komponentu jest OnPush i masz:

   var obj = {
     name:'Milad'
   };

A potem aktualizujesz / mutujesz to tak:

  obj.name = "a new name";

To nie będzie aktualizować obj odniesienia, stąd wykrywania zmian nie zamierzam biegać, więc widok nie jest odzwierciedlając aktualizacji / mutacji.

W takim przypadku musisz ręcznie powiedzieć Angularowi, aby sprawdził i zaktualizował widok (markForCheck);

Więc jeśli zrobiłeś to:

  obj.name = "a new name";

Musisz to zrobić:

  this.cd.markForCheck();

Raczej poniżej spowodowałoby uruchomienie wykrywania zmian:

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

Który całkowicie zastąpił poprzedni obiekt nowym {};

2- Zdarzenie zostało uruchomione, np. Kliknięcie lub coś podobnego albo którykolwiek z komponentów podrzędnych wyemitował zdarzenie.

Wydarzenia takie jak:

  • Kliknij
  • Keyup
  • Wydarzenia subskrypcyjne
  • itp.

Krótko mówiąc:

  • Użyj, detectChanges()gdy zaktualizowałeś model po uruchomieniu przez angular wykrywania zmian lub jeśli aktualizacja w ogóle nie była w świecie kątowym.

  • Użyj, markForCheck()jeśli używasz OnPush i pomijasz ChangeDetectionStrategyprzez mutację niektórych danych lub zaktualizowałeś model wewnątrz setTimeout ;

Milad
źródło
6
Więc jeśli zmienisz ten obiekt, widok nie zostanie zaktualizowany, a nawet jeśli uruchomisz funkcję detekcji zmian, nie będzie działać, ponieważ nie było żadnych zmian - to nieprawda. detectChangeswidok aktualizacji. Zobacz to szczegółowe wyjaśnienie .
Max Koretskyi
Odnośnie markForCheck we wniosku też nie jest poprawne. Oto zmodyfikowany przykład z tego pytania , nie wykrywa zmian obiektów za pomocą OnPush i markForCheck. Ale ten sam przykład zadziała, jeśli nie ma strategii OnPush.
Estus Flask
@Maximus, Odnośnie twojego pierwszego komentarza, przeczytałem twój post, dzięki za to, że był dobry. Ale w swoim wyjaśnieniu mówisz, że jeśli strategia to OnPush, oznacza to, że jeśli this.cdMode === ChangeDetectorStatus.Checkednie zaktualizuje widoku, dlatego użyjesz markForCheck.
Milad
A jeśli chodzi o twoje linki do plunkera, oba przykłady działają dobrze dla mnie, nie wiem, co masz na myśli
Milad
@Milad, te komentarze pochodzą od @estus :). Mój był o detectChanges. A cdModew Angular nie ma 4.x.x. Piszę o tym w swoim artykule. Cieszę się, że Ci się podobało. Nie zapomnij, że możesz polecić to na medium lub śledzić mnie :)
Max Koretskyi
99

Największa różnica między nimi polega na tym, że detectChanges()faktycznie wyzwala wykrywanie zmian, a markForCheck()nie powoduje wykrywania zmian.

DetectChanges

Ten służy do uruchamiania wykrywania zmian w drzewie komponentów, zaczynając od składnika, który jest uruchamiany detectChanges(). Tak więc wykrywanie zmian będzie działać dla bieżącego składnika i wszystkich jego elementów podrzędnych. Angular przechowuje odwołania do głównego drzewa komponentów w, ApplicationRefa gdy ma miejsce jakakolwiek operacja asynchroniczna, wyzwala wykrywanie zmian w tym głównym komponencie za pomocą metody opakowującej 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()); <------------------

viewtutaj jest widok komponentu głównego. Może istnieć wiele komponentów głównych, jak opisałem w sekcji Jakie są konsekwencje ładowania wielu komponentów .

@milad opisał powody, dla których potencjalnie może być konieczne ręczne wywołanie wykrywania zmian.

markForCheck

Jak powiedziałem, ten facet w ogóle nie uruchamia wykrywania zmian. Po prostu przechodzi w górę z bieżącego komponentu do komponentu głównego i aktualizuje ich stan widoku do ChecksEnabled. Oto kod źródłowy:

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

Rzeczywiste wykrywanie zmian dla komponentu nie jest planowane, ale gdy nastąpi to w przyszłości (w ramach bieżącego lub następnego cyklu CD), widoki komponentu macierzystego zostaną sprawdzone, nawet jeśli miały odłączone detektory zmian. Detektory zmian można odłączać, używając cd.detach()lub określając OnPushstrategię wykrywania zmian. Wszystkie natywne programy obsługi zdarzeń zaznaczają wszystkie widoki komponentów nadrzędnych do sprawdzenia.

To podejście jest często używane w ngDoCheckhaku cyklu życia. Możesz przeczytać więcej w Jeśli uważasz, że ngDoCheckoznacza to, że Twój komponent jest sprawdzany - przeczytaj ten artykuł .

Zobacz też Wszystko, co musisz wiedzieć o wykrywaniu zmian w Angular, aby uzyskać więcej informacji.

Max Koretskyi
źródło
1
Dlaczego DetectChanges działa na składniku i jego elementach podrzędnych, a markForCheck na składniku i elementach nadrzędnych?
pablo
@pablo, to jest zgodne z projektem. Nie znam uzasadnienia
Max Koretskyi
@ AngularInDepth.com czy changeetection blokuje interfejs użytkownika, jeśli jest bardzo intensywne przetwarzanie?
alt255
1
@jerry, zalecanym podejściem jest użycie potoku asynchronicznego, który wewnętrznie śledzi subskrypcję i każdą nową wartość wyzwalającą markForCheck. Więc jeśli nie używasz potoku asynchronicznego, prawdopodobnie powinieneś użyć tego. Należy jednak pamiętać, że aktualizacja sklepu powinna nastąpić w wyniku jakiegoś zdarzenia asynchronicznego, aby rozpocząć wykrywanie zmian. Tak jest najczęściej. Ale są wyjątki blog.angularindepth.com/…
Max Koretskyi
1
@MaxKoretskyiakaWizard dzięki za odpowiedź. Tak, aktualizacja sklepu jest w większości wynikiem pobierania lub ustawienia wcześniejszego pobierania. i po pobraniu ... ale nie zawsze możemy użyć, async pipeponieważ w subskrybowaniu zwykle mamy kilka rzeczy do zrobienia, na przykład call setFromValues do some comparison... a jeśli asyncsama wywołuje, markForCheckjaki jest problem, jeśli nazywamy to sami? ale znowu zwykle mamy 2-3 lub czasami więcej selektorów w ngOnInitpobieraniu różnych danych ... i wzywamy markForCheckje wszystkie .. czy to w porządku?
Jerry
0

cd.detectChanges() uruchomi wykrywanie zmian natychmiast od bieżącego składnika aż do jego elementów podrzędnych.

cd.markForCheck()nie uruchomi wykrywania zmian, ale oznaczy swoich przodków jako wymagających wykrywania zmian. Następnym razem, gdy wykrywanie zmian zostanie uruchomione w dowolnym miejscu, będzie działać również dla zaznaczonych komponentów.

  • Jeśli chcesz zmniejszyć liczbę przypadków, w których wykrywanie zmian jest nazywane użyj cd.markForCheck(). Często zmiany wpływają na wiele komponentów i gdzieś zostanie wywołane wykrywanie zmian. Zasadniczo mówisz: po prostu upewnijmy się, że ten komponent jest również aktualizowany, gdy tak się stanie. (Widok jest natychmiast aktualizowany w każdym napisanym przeze mnie projekcie, ale nie w każdym teście jednostkowym).
  • Jeśli nie możesz być pewien, że obecniecd.detectChanges() nie działa wykrywanie zmian, użyj . błąd w takim przypadku. Prawdopodobnie oznacza to, że próbujesz edytować stan komponentu przodka, który działa wbrew założeniom, zgodnie z którymi zostało zaprojektowane wykrywanie zmian w Angular. cd.markForCheck()detectChanges()
  • Jeśli ważne jest, aby widok był aktualizowany synchronicznie przed jakąś inną akcją, użyj detectChanges(). markForCheck()może nie aktualizować widoku na czas. Testowanie jednostkowe coś wpływa na widok, na przykład może wymagać ręcznego wywołania, fixture.detectChanges()gdy nie było to konieczne w samej aplikacji.
  • Jeśli zmieniasz stan w komponencie, który ma więcej elementów nadrzędnych niż podrzędnych, możesz uzyskać wzrost wydajności dzięki użyciu, detectChanges()ponieważ nie używasz niepotrzebnie wykrywania zmian w elementach nadrzędnych składnika.
Kevin Beal
źródło