Was ist der Unterschied zwischen markForCheck () und detectChanges ()

174

Was ist der Unterschied zwischen ChangeDetectorRef.markForCheck()und ChangeDetectorRef.detectChanges()?

Ich habe nur Informationen über SO bezüglich des Unterschieds NgZone.run()zwischen diesen beiden Funktionen gefunden, aber nicht zwischen diesen beiden Funktionen.

Für Antworten mit nur einem Verweis auf das Dokument veranschaulichen Sie bitte einige praktische Szenarien, um eine über die andere zu wählen.

Parlament
quelle
@Milad Woher weißt du, dass er es abgelehnt hat? Es gibt viele Leute, die diese Seite lesen.
Auf Wiedersehen StackExchange
2
@FrankerZ, weil ich schrieb und die Abwertung sah und eine Sekunde später die Frage aktualisiert wurde: "Für Antworten mit nur einem Verweis auf das Dokument veranschaulichen Sie bitte einige praktische Szenarien, um eine über die andere zu wählen. Das wird helfen, dies zu klären." in meinen Gedanken".
Milad
3
Die Ablehnung bestand darin, Sie zu motivieren, die ursprüngliche Antwort zu vervollständigen, die nur aus den Dokumenten kopiert und eingefügt wurde, die ich bereits gesehen habe. Und es hat funktioniert! Jetzt hat die Antwort viel Klarheit und ist die akzeptierte Antwort, danke
Parlament
3
Was für ein hinterhältiger Plan @parliament!
HankCa

Antworten:

234

Aus Dokumenten:

detectChanges (): void

Überprüft den Änderungsdetektor und seine Kinder.

Wenn sich in Ihrem Modell (Ihrer Klasse) etwas geändert hat, das jedoch die Ansicht nicht widerspiegelt, müssen Sie Angular möglicherweise benachrichtigen, um diese Änderungen zu erkennen (lokale Änderungen zu erkennen) und die Ansicht zu aktualisieren.

Mögliche Szenarien könnten sein:

1- Der Änderungsdetektor ist von der Ansicht getrennt (siehe Abnehmen ).

2- Es wurde ein Update durchgeführt, das sich jedoch nicht in der Angular Zone befand. Daher weiß Angular nichts davon.

Zum Beispiel, wenn eine Drittanbieterfunktion Ihr Modell aktualisiert hat und Sie die Ansicht danach aktualisieren möchten.

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

Da sich dieser Code (wahrscheinlich) außerhalb der Angular-Zone befindet, müssen Sie höchstwahrscheinlich sicherstellen, dass die Änderungen erkannt und die Ansicht aktualisiert werden.

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

HINWEIS :

Es gibt andere Möglichkeiten, die oben genannte Arbeit zu machen, mit anderen Worten, es gibt andere Möglichkeiten, diese Änderung in den Winkeländerungszyklus zu bringen.

** Sie können diese Drittanbieterfunktion in eine zone.run einbinden:

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

** Sie können die Funktion in ein setTimeout einschließen:

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

3- Es gibt auch Fälle, in denen Sie das Modell aktualisieren, nachdem das abgeschlossen change detection cycleist. In diesen Fällen erhalten Sie diesen gefürchteten Fehler:

"Ausdruck hat sich geändert, nachdem er überprüft wurde";

Dies bedeutet im Allgemeinen (aus der Sprache Angular2):

Ich habe eine Änderung in Ihrem Modell gesehen, die durch eine meiner akzeptierten Methoden (Ereignisse, XHR-Anforderungen, setTimeout und ...) verursacht wurde, und dann habe ich meine Änderungserkennung ausgeführt, um Ihre Ansicht zu aktualisieren, und ich habe sie beendet, aber dann gab es eine andere Funktion in Ihrem Code, der das Modell erneut aktualisiert hat, und ich möchte meine Änderungserkennung nicht erneut ausführen, da es keine schmutzigen Überprüfungen wie AngularJS mehr gibt: D und wir sollten einen Einweg-Datenfluss verwenden!

Sie werden auf jeden Fall auf diesen Fehler stoßen: P.

Einige Möglichkeiten, dies zu beheben:

1- Richtige Methode : Stellen Sie sicher, dass sich die Aktualisierung innerhalb des Änderungserkennungszyklus befindet (Angular2-Aktualisierungen sind ein einmaliger Ablauf, aktualisieren Sie das Modell danach nicht mehr und verschieben Sie Ihren Code an einen besseren Ort / zu einer besseren Zeit).

2- Fauler Weg : Führen Sie nach diesem Update detectChanges () aus, um angle2 glücklich zu machen. Dies ist definitiv nicht der beste Weg, aber da Sie gefragt haben, welche Szenarien möglich sind, ist dies einer von ihnen.

Auf diese Weise sagen Sie: Ich weiß aufrichtig, dass Sie die Änderungserkennung ausgeführt haben, aber ich möchte, dass Sie dies erneut tun, da ich nach Abschluss der Überprüfung im laufenden Betrieb etwas aktualisieren musste.

3- Fügen Sie den Code in a ein setTimeout, da er setTimeoutnach Zonen gepatcht ist und detectChangesnach Abschluss ausgeführt wird.


Aus den Dokumenten

markForCheck() : void

Markiert alle Vorfahren von ChangeDetectionStrategy als zu prüfend.

Dies wird hauptsächlich benötigt, wenn die ChangeDetectionStrategy Ihrer Komponente OnPush ist .

OnPush selbst bedeutet, dass die Änderungserkennung nur ausgeführt wird, wenn eines der folgenden Ereignisse eingetreten ist:

1- Einer der @ -Eingänge der Komponente wurde vollständig durch einen neuen Wert ersetzt oder einfach ausgedrückt, wenn sich die Referenz der @ Input-Eigenschaft insgesamt geändert hat.

Wenn ChangeDetectionStrategy Ihrer Komponente OnPush ist und Sie haben:

   var obj = {
     name:'Milad'
   };

Und dann aktualisieren / mutieren Sie es wie folgt:

  obj.name = "a new name";

Dadurch wird die obj- Referenz nicht aktualisiert , daher wird die Änderungserkennung nicht ausgeführt, daher spiegelt die Ansicht nicht die Aktualisierung / Mutation wider .

In diesem Fall müssen Sie Angular manuell anweisen, die Ansicht zu überprüfen und zu aktualisieren (markForCheck).

Wenn Sie dies getan haben:

  obj.name = "a new name";

Sie müssen dies tun:

  this.cd.markForCheck();

Im Folgenden wird vielmehr eine Änderungserkennung ausgeführt:

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

Was das vorherige Objekt vollständig durch ein neues ersetzte {};

2- Ein Ereignis wurde ausgelöst, wie ein Klick oder ähnliches, oder eine der untergeordneten Komponenten hat ein Ereignis ausgelöst.

Veranstaltungen wie:

  • Klicken
  • Keyup
  • Abonnementereignisse
  • etc.

Also kurz gesagt:

  • Verwenden detectChanges()Sie diese Option, wenn Sie das Modell aktualisiert haben, nachdem Angular die Änderungserkennung ausgeführt hat, oder wenn sich das Update überhaupt nicht in der Winkelwelt befunden hat.

  • Verwenden markForCheck()Sie diese Option, wenn Sie OnPush verwenden und das umgehen, ChangeDetectionStrategyindem Sie einige Daten mutieren, oder wenn Sie das Modell in einem setTimeout aktualisiert haben .

Milad
quelle
6
Wenn Sie dieses Objekt mutieren, wird die Ansicht nicht aktualisiert, und selbst wenn Sie detectChanges ausführen, funktioniert sie nicht, da keine Änderungen vorgenommen wurden - dies ist nicht der Fall. detectChangesaktualisiert die Ansicht. Siehe diese ausführliche Erklärung .
Max Koretskyi
In Bezug auf markForCheck in der Schlussfolgerung ist es auch nicht korrekt. Hier ist ein modifiziertes Beispiel aus dieser Frage. Mit OnPush und markForCheck werden keine Objektänderungen erkannt. Aber das gleiche Beispiel wird funktionieren , wenn es keine OnPush Strategie.
Estus Flask
@ Maximus, In Bezug auf deinen ersten Kommentar habe ich deinen Beitrag gelesen, danke, dass er gut war. Aber in Ihrer Erklärung sagen Sie, wenn die Strategie OnPush ist, bedeutet dies this.cdMode === ChangeDetectorStatus.Checked, dass Sie markForCheck verwenden würden, wenn die Ansicht nicht aktualisiert wird.
Milad
Und in Bezug auf Ihre Links zu Plunker funktionieren beide Beispiele gut für mich, ich weiß nicht, was Sie meinen
Milad
@Milad, diese Kommentare kamen von @estus :). Meins war ungefähr detectChanges. Und es gibt keine cdModein Angular4.x.x . Ich schreibe darüber in meinem Artikel. Ich bin froh, dass es dir gefallen hat. Vergiss nicht, dass du es auf Medium empfehlen kannst oder folge mir :)
Max Koretskyi
99

Der größte Unterschied zwischen beiden besteht darin, dass detectChanges()die Änderungserkennung tatsächlich markForCheck()ausgelöst wird , während die Änderungserkennung nicht ausgelöst wird.

detectChanges

Diese wird verwendet, um die Änderungserkennung für den Komponentenbaum auszuführen, beginnend mit der Komponente, die Sie auslösen detectChanges(). Die Änderungserkennung wird also für die aktuelle Komponente und alle ihre untergeordneten Komponenten ausgeführt. Angular enthält Verweise auf den Stammkomponentenbaum im ApplicationRefund wenn eine asynchrone Operation ausgeführt wird, wird die Änderungserkennung für diese Stammkomponente über eine Wrapper-Methode ausgelöst 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()); <------------------

viewHier ist die Stammkomponentenansicht. Es kann viele Stamm Komponenten sein , wie ich in der beschrieben Was die Auswirkungen des Bootstrapping mehrere Komponenten sind .

@milad beschrieb die Gründe, warum Sie möglicherweise die Änderungserkennung manuell auslösen müssen.

markForCheck

Wie gesagt, dieser Typ löst überhaupt keine Änderungserkennung aus. Es geht einfach von der aktuellen Komponente zur Stammkomponente nach oben und aktualisiert ihren Ansichtsstatus auf ChecksEnabled. Hier ist der Quellcode:

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

Die tatsächliche Änderungserkennung für die Komponente ist nicht geplant, aber wenn dies in Zukunft geschehen wird (entweder als Teil des aktuellen oder des nächsten CD-Zyklus), werden die Ansichten der übergeordneten Komponenten überprüft, selbst wenn sie Änderungsdetektoren getrennt haben. Änderungsdetektoren können entweder mithilfe cd.detach()oder unter Angabe einer OnPushÄnderungserkennungsstrategie entfernt werden. Alle nativen Ereignishandler markieren alle übergeordneten Komponentenansichten zur Überprüfung.

Dieser Ansatz wird häufig im ngDoCheckLifecycle-Hook verwendet. Weitere Informationen finden Sie unter Wenn Sie der Meinung sind, ngDoCheckdass Ihre Komponente überprüft wird, lesen Sie diesen Artikel .

Weitere Informationen finden Sie unter Alles, was Sie über die Änderungserkennung in Angular wissen müssen .

Max Koretskyi
quelle
1
Warum funktioniert detectChanges für die Komponente und ihre untergeordneten Elemente, während markForCheck für die Komponente und ihre Vorfahren gilt?
Pablo
@pablo, das ist beabsichtigt. Ich bin nicht wirklich vertraut mit der Begründung
Max Koretskyi
@ AngularInDepth.com blockiert die geänderte Erkennung die Benutzeroberfläche, wenn eine sehr intensive Verarbeitung erfolgt?
alt255
1
@jerry, der empfohlene Ansatz ist die Verwendung einer asynchronen Pipe, die das Abonnement intern und bei jedem neuen Wertauslöser verfolgt markForCheck. Wenn Sie also keine asynchrone Pipe verwenden, sollten Sie diese wahrscheinlich verwenden. Beachten Sie jedoch, dass die Speicheraktualisierung aufgrund eines asynchronen Ereignisses erfolgen sollte, damit die Änderungserkennung gestartet wird. Das ist meistens immer der Fall. Aber es gibt Ausnahmen blog.angularindepth.com/…
Max Koretskyi
1
@ MaxKoretskyiakaWizard danke für die Antwort. Ja, das Store-Update ist meistens das Ergebnis eines Abrufs oder der Einstellung isFetching before. und nach dem Abrufen .. aber wir können nicht immer verwenden, async pipeda wir innerhalb des Abonnements normalerweise ein paar Dinge zu tun haben call setFromValues do some comparison.. und wenn asyncselbst anruft, markForCheckwas ist das Problem, wenn wir es selbst nennen? Aber normalerweise haben wir 2-3 oder manchmal mehr Selektoren ngOnInit, um unterschiedliche Daten zu erhalten ... und wir rufen markForCheckalle an. Ist das in Ordnung?
Jerry
0

cd.detectChanges() führt die Änderungserkennung sofort von der aktuellen Komponente über ihre Nachkommen aus.

cd.markForCheck()führt keine Änderungserkennung durch, markiert jedoch seine Vorfahren als erforderlich, um die Änderungserkennung auszuführen. Wenn die Änderungserkennung das nächste Mal irgendwo ausgeführt wird, wird sie auch für die markierten Komponenten ausgeführt.

  • Wenn Sie die Häufigkeit reduzieren möchten, wird die Änderungserkennung als Verwendung bezeichnet cd.markForCheck(). Oft wirken sich Änderungen auf mehrere Komponenten aus, und irgendwo wird die Änderungserkennung aufgerufen. Sie sagen im Wesentlichen: Stellen wir einfach sicher, dass diese Komponente in diesem Fall auch aktualisiert wird. (Die Ansicht wird sofort in jedem Projekt aktualisiert, das ich geschrieben habe, aber nicht in jedem Komponententest).
  • Wenn Sie sich nicht sicher sein , dass cd.detectChanges()nicht ist derzeit laufende Änderungserkennung, Verwendung cd.markForCheck(). detectChanges()wird in diesem Fall Fehler. Dies bedeutet wahrscheinlich, dass Sie versuchen, den Status einer Vorfahrenkomponente zu bearbeiten, was den Annahmen widerspricht, auf denen die Änderungserkennung von Angular basiert.
  • Wenn es wichtig ist, dass die Ansicht vor einer anderen Aktion synchron aktualisiert wird, verwenden Sie detectChanges(). markForCheck()Möglicherweise wird Ihre Ansicht nicht rechtzeitig aktualisiert. Beim Testen von Einheiten, die sich auf Ihre Ansicht auswirken, müssen Sie möglicherweise manuell anrufen, fixture.detectChanges()wenn dies in der App selbst nicht erforderlich war.
  • Wenn Sie den Status in einer Komponente mit mehr Vorfahren als Nachkommen detectChanges()ändern, können Sie durch die Verwendung eine Leistungssteigerung erzielen, da Sie die Änderungserkennung für die Vorfahren der Komponente nicht unnötig ausführen.
Kevin Beal
quelle