Qual è la differenza tra markForCheck () e detectChanges ()


174

Qual è la differenza tra ChangeDetectorRef.markForCheck()e ChangeDetectorRef.detectChanges()?

Ho trovato solo informazioni su SO in merito alla differenza tra NgZone.run(), ma non tra queste due funzioni.

Per le risposte con solo un riferimento al documento, si prega di illustrare alcuni scenari pratici per scegliere uno sopra l'altro.



@Milad Come fai a sapere che l'ha sottovalutato? Ci sono molte persone che sfogliano questo sito.
Arrivederci StackExchange il

2
@FrankerZ, perché stavo scrivendo e ho visto il downvote e un secondo dopo la domanda è stata aggiornata dicendo che "Per le risposte con solo un riferimento al documento, si prega di illustrare alcuni scenari pratici per scegliere l'uno sull'altro? Ciò contribuirà a chiarirlo nella mia mente ".
Milad,

3
Il downvote è stato quello di incentivarti a completare la risposta originale che è stata appena copiata e incollata dai documenti che ho già visto. E ha funzionato! Ora la risposta ha molta chiarezza ed è la risposta accettata, grazie
parlamento

3
che piano subdolo @parlamento!
HankCa,

Risposte:


234

Da documenti:

detectChanges (): void

Verifica il rilevatore di modifiche e i relativi figli.

Significa che se c'è un caso in cui qualcosa all'interno del tuo modello (la tua classe) è cambiato ma non ha rispecchiato la vista, potresti aver bisogno di avvisare Angular per rilevare quelle modifiche (rilevare le modifiche locali) e aggiornare la vista.

I possibili scenari potrebbero essere:

1- Il rilevatore di modifiche è staccato dalla vista (vedi staccare )

2- Si è verificato un aggiornamento ma non è stato all'interno della zona angolare, pertanto Angular non lo conosce.

Come quando una funzione di terze parti ha aggiornato il tuo modello e successivamente desideri aggiornare la vista.

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

Poiché questo codice è al di fuori della zona di Angular (probabilmente), molto probabilmente è necessario assicurarsi di rilevare le modifiche e aggiornare la vista, quindi:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

NOTA :

Esistono altri modi per far funzionare sopra, in altre parole, ci sono altri modi per portare quel cambiamento all'interno del ciclo del cambiamento angolare.

** È possibile racchiudere quella funzione di terze parti all'interno di una zona.run:

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

** È possibile racchiudere la funzione all'interno di un setTimeout:

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

3- Ci sono anche casi in cui si aggiorna il modello al change detection cycletermine del processo, in cui in questi casi viene visualizzato questo temuto errore:

"L'espressione è cambiata dopo che è stata controllata";

Ciò significa generalmente (dal linguaggio Angular2):

Ho visto un cambiamento nel tuo modello che è stato causato da uno dei miei modi accettati (eventi, richieste XHR, setTimeout e ...) e poi ho eseguito il rilevamento delle modifiche per aggiornare la tua vista e l'ho finito, ma poi c'è stato un altro nel tuo codice che ha aggiornato di nuovo il modello e non voglio eseguire nuovamente il rilevamento delle modifiche perché non esiste più un controllo sporco come AngularJS: D e dovremmo usare un flusso di dati a senso unico!

Incontrerai sicuramente questo errore: P.

Un paio di modi per risolverlo:

1- Modo corretto : assicurarsi che l'aggiornamento sia all'interno del ciclo di rilevamento delle modifiche (gli aggiornamenti di Angular2 sono un flusso unidirezionale che si verifica una volta, non aggiornare il modello successivamente e spostare il codice in un luogo / orario migliore).

2- Modo pigro : esegui detectChanges () dopo quell'aggiornamento per rendere felice angular2, questo non è sicuramente il modo migliore, ma come hai chiesto quali sono i possibili scenari, questo è uno di questi.

In questo modo stai dicendo: So sinceramente che hai eseguito il rilevamento delle modifiche, ma voglio che tu lo faccia di nuovo perché ho dovuto aggiornare qualcosa al volo dopo aver terminato il controllo.

3- Inserisci il codice in a setTimeout, perché setTimeoutè patchato per zona e verrà eseguito detectChangesal termine.


Dai documenti

markForCheck() : void

Contrassegna tutti gli antenati ChangeDetectionStrategy come da verificare.

Ciò è necessario soprattutto quando ChangeDetectionStrategy del componente è OnPush .

OnPush stesso significa, eseguire il rilevamento delle modifiche solo se si è verificato uno di questi:

1- Uno degli @input del componente è stato completamente sostituito con un nuovo valore o, semplicemente, se il riferimento della proprietà @Input è cambiato del tutto.

Quindi se ChangeDetectionStrategy del tuo componente è OnPush e hai:

   var obj = {
     name:'Milad'
   };

E poi lo aggiorni / muti come:

  obj.name = "a new name";

Questo non aggiornerà il riferimento obj , quindi il rilevamento delle modifiche non verrà eseguito, quindi la vista non riflette l'aggiornamento / mutazione.

In questo caso devi dire manualmente ad Angular di controllare e aggiornare la vista (markForCheck);

Quindi se hai fatto questo:

  obj.name = "a new name";

Devi fare questo:

  this.cd.markForCheck();

Piuttosto, di seguito causerebbe l'esecuzione di un rilevamento delle modifiche:

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

Che ha completamente sostituito il precedente obj con un nuovo {};

2- Un evento è stato generato, come un clic o qualcosa del genere o uno qualsiasi dei componenti figlio ha emesso un evento.

Eventi come:

  • Clic
  • keyup
  • Eventi in abbonamento
  • eccetera.

Quindi in breve:

  • Utilizzalo detectChanges()quando hai aggiornato il modello dopo che Angular ha eseguito il rilevamento delle modifiche o se l'aggiornamento non è stato affatto nel mondo angolare.

  • Utilizzare markForCheck()se si utilizza OnPush e si sta aggirando il ChangeDetectionStrategymutando alcuni dati o se si è aggiornato il modello all'interno di un setTimeout ;


6
Quindi, se muti quell'oggetto, la vista non verrà aggiornata e anche se avvii detectChanges, non funzionerà perché non ci sono stati cambiamenti - questo non è vero. detectChangesvista degli aggiornamenti. Vedi questa spiegazione approfondita .
Max Koretskyi,

Per quanto riguarda markForCheck nella conclusione, non è anche accurato. Ecco l' esempio modificato di questa domanda , non rileva le modifiche agli oggetti con OnPush e markForCheck. Ma lo stesso esempio funzionerà se non esiste una strategia OnPush.
Estus Flask

@Maximus, per quanto riguarda il tuo primo commento, ho letto il tuo post, grazie per il fatto che è stato bello. Ma nella tua spiegazione stai dicendo che se la strategia è OnPush, significa che se this.cdMode === ChangeDetectorStatus.Checkednon aggiornerà la vista, ecco perché dovresti usare markForCheck.
Milad,

E per quanto riguarda i tuoi collegamenti con il plunker, entrambi gli esempi stanno funzionando bene per me, non so cosa vuoi dire
Milad,

@Milad, quei commenti sono arrivati ​​da @estus :). Il mio era circa detectChanges. E non c'è cdModein angolare 4.x.x. Ne scrivo nel mio articolo. Lieto che ti sia piaciuto. Non dimenticare che puoi consigliarlo a medio termine o seguirmi :)
Max Koretskyi,

99

La più grande differenza tra i due è che in detectChanges()realtà attiva il rilevamento delle modifiche, mentre markForCheck()non attiva il rilevamento delle modifiche.

detectChanges

Questo viene utilizzato per eseguire il rilevamento delle modifiche per l'albero dei componenti a partire dal componente su cui si attiva detectChanges(). Quindi il rilevamento delle modifiche verrà eseguito per il componente corrente e tutti i suoi figli. Angolare contiene riferimenti all'albero del componente radice in ApplicationRefe quando si verifica un'operazione asincrona, innesca il rilevamento delle modifiche su questo componente radice tramite un metodo wrapper 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()); <------------------

viewecco la vista del componente principale. Possono esserci molti componenti root come ho descritto in Quali sono le implicazioni del bootstrap di più componenti .

@milad ha descritto i motivi per cui potrebbe essere necessario attivare manualmente il rilevamento delle modifiche.

markForCheck

Come ho detto, questo ragazzo non attiva affatto il rilevamento delle modifiche. Passa semplicemente dal componente corrente al componente radice e aggiorna il loro stato di visualizzazione a ChecksEnabled. Ecco il codice sorgente:

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

Il rilevamento delle modifiche effettive per il componente non è pianificato ma quando avverrà in futuro (come parte del ciclo CD corrente o successivo), le viste dei componenti principali verranno controllate anche se avevano rilevatori di modifiche staccati. I rilevatori di modifiche possono essere staccati utilizzando cd.detach()o specificando la OnPushstrategia di rilevazione delle modifiche. Tutti i gestori di eventi nativi contrassegnano tutte le viste dei componenti principali per il controllo.

Questo approccio viene spesso utilizzato nel ngDoCheckgancio del ciclo di vita. Puoi leggere di più in Se pensi che il ngDoChecktuo componente sia in fase di verifica, leggi questo articolo .

Vedi anche Tutto quello che devi sapere sul rilevamento delle modifiche in Angolare per maggiori dettagli.


1
Perché detectChanges funziona sul componente e sui relativi figli mentre markForCheck sul componente e sugli antenati?
pablo,

@pablo, questo è design. Non ho molta familiarità con la logica
Max Koretskyi,

@ AngularInDepth.com changeetection blocca l'interfaccia utente in caso di elaborazione molto intensiva?
alt255,

1
@jerry, l'approccio consigliato è l'uso della pipe asincrona, che tiene traccia internamente dell'abbonamento e innesca ogni nuovo valore markForCheck. Quindi, se non stai usando la pipa asincrona, è probabilmente quello che dovresti usare. Tuttavia, tieni presente che l'aggiornamento del negozio dovrebbe avvenire a seguito di un evento asincrono per l'avvio del rilevamento delle modifiche. Questo è quasi sempre il caso. Ma ci sono eccezioni blog.angularindepth.com/…
Max Koretskyi,

1
@MaxKoretskyiakaWizard grazie per la risposta. Sì, l'aggiornamento del negozio è principalmente il risultato di un recupero o l'impostazione è Recupero prima. e dopo il recupero .. ma non possiamo sempre usarlo async pipepoiché all'interno dell'abbonamento di solito abbiamo alcune cose da fare come call setFromValues do some comparison.. e se asyncstesso chiama markForCheckqual è il problema se lo chiamiamo noi stessi? ma di nuovo di solito abbiamo 2-3 o talvolta più selettori per ngOnInitottenere dati diversi ... e li chiamiamo markForCheckin tutti loro ... va bene?
jerry,

0

cd.detectChanges() eseguirà immediatamente il rilevamento delle modifiche dal componente corrente verso il basso attraverso i suoi discendenti.

cd.markForCheck()non eseguirà il rilevamento delle modifiche, ma contrassegnerà i suoi antenati come se dovessero eseguire il rilevamento delle modifiche. La prossima volta che il rilevamento delle modifiche viene eseguito ovunque, verrà eseguito anche per i componenti contrassegnati.

  • Se si desidera ridurre il numero di volte in cui viene chiamato il rilevamento delle modifiche, utilizzare cd.markForCheck(). Spesso, le modifiche influiscono su più componenti e da qualche parte verrà chiamato il rilevamento delle modifiche. Stai dicendo in sostanza: diciamo solo assicurarsi che questo componente è stato anche aggiornato quando ciò accade. (La vista viene immediatamente aggiornata in ogni progetto che ho scritto, ma non in ogni unit test).
  • Se non si è certi che al momentocd.detectChanges() non sia in esecuzione il rilevamento delle modifiche, utilizzare cd.markForCheck(). detectChanges()errore in quel caso. Questo probabilmente significa che stai provando a modificare lo stato di un componente antenato, che sta lavorando contro i presupposti secondo cui il rilevamento delle modifiche di Angular è progettato intorno.
  • Se è fondamentale che la vista si aggiorni in modo sincrono prima di qualche altra azione, utilizzare detectChanges(). markForCheck()potrebbe effettivamente non aggiornare la visualizzazione in tempo. Un test di unità che influisce sulla vista, ad esempio, potrebbe richiedere la chiamata manuale fixture.detectChanges()quando ciò non era necessario nell'app stessa.
  • Se si modifica lo stato in un componente con più antenati rispetto ai discendenti, è possibile ottenere un aumento delle prestazioni utilizzando detectChanges()poiché non si esegue inutilmente il rilevamento delle modifiche sugli antenati del componente.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.