В чем разница между markForCheck () и detectChanges ()

174

В чем разница между ChangeDetectorRef.markForCheck()иChangeDetectorRef.detectChanges() ?

Я только нашел информацию на SO о разнице междуNgZone.run() между этими двумя функциями, но не об этих.

Для ответов со ссылкой только на документ, пожалуйста, проиллюстрируйте некоторые практические сценарии, чтобы выбрать один из других.

парламент
источник
@ Милад Откуда ты знаешь, что он отрицал это? Есть много людей, которые просматривают этот сайт.
До свидания StackExchange
2
@FrankerZ, потому что я писал, и я увидел отрицательное голосование, а секунду спустя вопрос был обновлен, сказав, что «Для ответов только со ссылкой на документ, пожалуйста, проиллюстрируйте некоторые практические сценарии, чтобы выбрать один из других? Это поможет прояснить это в моей голове".
Милад
3
Недостатком было побудить вас завершить первоначальный ответ, который был просто скопирован и вставлен из документов, которые я уже видел. И это сработало! Теперь ответ имеет много ясности и это общепринятый ответ, спасибо:)
парламент
3
какой коварный план @парламент!
HankCa

Ответы:

234

Из документов:

detectChanges (): void

Проверяет детектор изменений и его дочерние элементы.

Это означает, что в случае, когда какая-либо вещь внутри вашей модели (вашего класса) изменилась, но она не отражала представление, вам может потребоваться уведомить Angular, чтобы обнаружить эти изменения (обнаружить локальные изменения) и обновить представление.

Возможные сценарии могут быть:

1- Детектор изменений отсоединен от вида (см. Отсоединение )

2- Произошло обновление, но оно не было внутри Angular Zone, поэтому Angular не знает об этом.

Например, когда сторонняя функция обновила вашу модель, и вы хотите обновить представление после этого.

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

Поскольку этот код находится за пределами зоны Angular (возможно), вам, скорее всего, необходимо убедиться, что обнаружены изменения и обновлено представление, таким образом:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

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

ПРИМЕЧАНИЕ :

Есть и другие способы заставить работать выше, иными словами, есть и другие способы внести это изменение в цикл угловых изменений.

** Вы можете обернуть эту стороннюю функцию внутри zone.run:

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

** Вы можете обернуть функцию внутри setTimeout:

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

3. Есть также случаи, когда вы обновляете модель после ее change detection cycleзавершения, где в этих случаях вы получаете эту страшную ошибку:

«Выражение изменилось после того, как оно было проверено»;

Обычно это означает (на языке Angular2):

Я увидел изменение в вашей модели, которое было вызвано одним из моих принятых способов (события, запросы XHR, setTimeout и ...), а затем я запустил обнаружение изменений, чтобы обновить ваше представление, и я завершил его, но затем появился другой функция в вашем коде, которая снова обновила модель, и я не хочу снова запускать обнаружение изменений, потому что больше нет грязной проверки, как AngularJS: D и мы должны использовать односторонний поток данных!

Вы обязательно столкнетесь с этой ошибкой: P.

Несколько способов это исправить:

1- Правильный путь : убедитесь, что обновление находится в цикле обнаружения изменений (обновления Angular2 - это односторонний поток, который происходит один раз, не обновляйте модель после этого и не перемещайте код в лучшее место / время).

2- Ленивый способ : после того, как это обновление запустит detectChanges (), чтобы сделать angular2 счастливым, это определенно не лучший способ, но, как вы спросили, каковы возможные сценарии, это один из них.

Таким образом, вы говорите: я искренне знаю, что вы запустили обнаружение изменений, но я хочу, чтобы вы сделали это снова, потому что мне нужно было что-то обновить на лету после того, как вы закончили проверку.

3- Поместите код внутри setTimeout, потому что setTimeoutпропатчен по зонам и будет работать detectChangesпосле его завершения.


Из документов

markForCheck() : void

Отмечает всех предков ChangeDetectionStrategy как проверяемые.

Это главным образом необходимо, когда ChangeDetectionStrategy вашего компонента - OnPush .

OnPush сам по себе означает, что запускать обнаружение изменений можно только в том случае, если произошло что-то из этого:

1- Один из @inputs компонента был полностью заменен новым значением, или, проще говоря, если ссылка на свойство @Input полностью изменилась.

Итак, если ChangeDetectionStrategy вашего компонента - OnPush, и тогда у вас есть:

   var obj = {
     name:'Milad'
   };

И затем вы обновляете / видоизменяете это как:

  obj.name = "a new name";

Это не обновит ссылку на obj , следовательно, обнаружение изменений не будет запущено, поэтому представление не отражает обновление / мутацию.

В этом случае вы должны вручную указать Angular для проверки и обновления представления (markForCheck);

Так что, если вы сделали это:

  obj.name = "a new name";

Вам нужно сделать это:

  this.cd.markForCheck();

Скорее, ниже приведёт к запуску обнаружения изменений:

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

Который полностью заменил предыдущий объект новым {};

2- Событие сработало, как щелчок, или что-то подобное, или любой из дочерних компонентов отправил событие.

События как:

  • щелчок
  • KeyUp
  • События подписки
  • и т.п.

Итак, вкратце:

  • Используйте, detectChanges()когда вы обновили модель после того, как angular запустил ее обнаружение изменений, или если обновление вообще не было в angular мире.

  • Используйте, markForCheck()если вы используете OnPush и обходите его ChangeDetectionStrategy, изменяя некоторые данные, или вы обновили модель внутри setTimeout ;

Milad
источник
6
Поэтому, если вы изменяете этот объект, представление не будет обновляться, и даже если вы запустите DetectChanges, оно не будет работать, потому что не было никаких изменений - это не так. detectChangesОбновление просмотра. Смотрите это подробное объяснение .
Макс Корецкий,
Что касается markForCheck в заключении, он тоже не точный. Вот модифицированный пример из этого вопроса , он не обнаруживает изменения объекта с помощью OnPush и markForCheck. Но тот же пример будет работать, если нет стратегии OnPush.
Estus
@ Maximus, По поводу вашего первого комментария я прочитал ваш пост, спасибо за то, что он был хорош. Но в своем объяснении вы говорите, что если стратегия OnPush, то есть если this.cdMode === ChangeDetectorStatus.Checkedона не будет обновлять представление, поэтому вы должны использовать markForCheck.
Милад
А Что касается ссылок на plunker, оба примера работают отлично для меня, я не знаю , что вы имеете в виду
Milad
@Milad, эти комментарии пришли от @estus :). Мой был около detectChanges. И нет cdModeв Angular 4.x.x. Я пишу об этом в своей статье. Рада что тебе понравилось. Не забывайте, что вы можете порекомендовать его на среднем или подписаться на меня :)
Макс Корецкий,
99

Самое большое различие между ними состоит в том, что detectChanges()фактически запускает обнаружение изменений, но markForCheck()не запускает обнаружение изменений.

detectChanges

Этот используется для запуска обнаружения изменений для дерева компонентов, начиная с компонента, который вы запускаете detectChanges(). Таким образом, обнаружение изменений будет выполняться для текущего компонента и всех его дочерних элементов. Angular содержит ссылки на корневое дерево компонентов в ApplicationRefи, когда происходит какая-либо асинхронная операция, она запускает обнаружение изменений в этом корневом компоненте с помощью метода-оболочки 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Вот представление корневого компонента. Корневых компонентов может быть много, как я описал в разделе « Каковы последствия начальной загрузки нескольких компонентов» .

@milad описал причины, по которым вам может понадобиться запускать обнаружение изменений вручную.

markForCheck

Как я уже сказал, этот парень вообще не вызывает обнаружение изменений. Он просто идет вверх от текущего компонента к корневому компоненту и обновляет их состояние просмотра ChecksEnabled. Вот исходный код:

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

Фактическое обнаружение изменений для компонента не запланировано, но когда это произойдет в будущем (как часть текущего или следующего цикла CD), будут проверены представления родительского компонента, даже если у них были отсоединенные детекторы изменений. Детекторы изменений могут быть отсоединены с помощью cd.detach()или путем указания OnPushстратегии обнаружения изменений. Все собственные обработчики событий отмечают все родительские представления компонентов для проверки.

Этот подход часто используется в ngDoCheckхуке жизненного цикла. Вы можете прочитать больше в разделе Если вы думаете, ngDoCheckчто ваш компонент проверяется - прочитайте эту статью .

Смотрите также Все, что вам нужно знать об обнаружении изменений в Angular для получения более подробной информации.

Макс Корецкий
источник
1
Почему deteChanges работает с компонентом и его дочерними элементами, а markForCheck - с компонентом и его предками?
Пабло
@ Пабло, это по замыслу. Я не очень знаком с обоснованием
Макс Корецкий
@ AngularInDepth.com блокирует ли changedetection пользовательский интерфейс, если происходит очень интенсивная обработка?
alt255
1
@jerry, рекомендуемый подход заключается в использовании асинхронного канала, который внутренне отслеживает подписку и при каждом новом срабатывании значения markForCheck. Так что, если вы не используете асинхронный канал, это, вероятно, то, что вы должны использовать. Однако имейте в виду, что обновление магазина должно происходить в результате некоторого асинхронного события для запуска обнаружения изменений. Это всегда так. Но есть исключения blog.angularindepth.com/…
Макс Корецкий
1
@MaxKoretskyiakaWizard спасибо за ответ. Да, обновление магазина в основном является результатом выборки или установки isFetching раньше. и после извлечения ... но мы не всегда можем использовать, async pipeтак как внутри подписки у нас обычно есть несколько вещей, которые нужно сделать, как call setFromValues do some comparison... и если asyncсам markForCheckвызов вызывает в чем проблема, если мы сами это называем? но опять же у нас обычно есть 2-3 или иногда больше селекторов в ngOnInitполучении различных данных ... и мы вызываем их markForCheckвсе ... это нормально?
Джерри
0

cd.detectChanges() сразу запустит обнаружение изменений от текущего компонента до его потомков.

cd.markForCheck()не будет запускать обнаружение изменений, но пометит своих предков как нужных для запуска обнаружения изменений. В следующий раз, когда обнаружение изменений будет выполнено где угодно, оно будет работать и для тех компонентов, которые были отмечены

  • Если вы хотите уменьшить количество раз, обнаружение изменений называется use cd.markForCheck(). Часто изменения затрагивают несколько компонентов, и где-то будет вызвано обнаружение изменений. По сути, вы говорите: давайте просто удостоверимся, что этот компонент также обновляется, когда это происходит. (Представление немедленно обновляется в каждом написанном мной проекте, но не в каждом модульном тесте).
  • Если вы не можете быть уверены, что cd.detectChanges() в данный момент не выполняется обнаружение изменений, используйте cd.markForCheck(). detectChanges()будет ошибка в этом случае. Это, вероятно, означает, что вы пытаетесь отредактировать состояние компонента-предка, который работает против предположений, на которые ориентировано обнаружение изменений Angular.
  • Если важно, чтобы представление обновлялось синхронно перед каким-либо другим действием, используйте detectChanges(). markForCheck()может не обновлять ваше мнение вовремя. Модульное тестирование что-то влияет на ваш вид, например, может потребоваться вручную вызвать, fixture.detectChanges()когда это не было необходимо в самом приложении.
  • Если вы изменяете состояние в компоненте с большим количеством предков, чем потомков, вы можете получить увеличение производительности, используя, detectChanges()так как вы не запускаете обнаружение изменений на предках компонента без необходимости.
Кевин Бил
источник