Яка різниця між markForCheck () та detectChanges ()


174

У чому різниця між ChangeDetectorRef.markForCheck()і ChangeDetectorRef.detectChanges()?

Я знайшов лише інформацію про SO щодо різниці NgZone.run()між цими двома функціями, але не між ними.

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



@Milad Звідки ти знаєш, що він визнав це? Є дуже багато людей, які вивчають цей сайт.
Goodbye StackExchange

2
@FrankerZ, тому що я писав, і я побачив голосову групу, а через секунду запитання було оновлено: "Для відповідей, які мають лише посилання на документ, проілюструйте, будь ласка, деякі практичні сценарії, щоб вибрати один над іншим? Це допоможе уточнити це" у мене на думці".
Мілад

3
Новий аналіз повинен був стимулювати вас виконати оригінальну відповідь, яку просто копіювали та вставляли з документів, які я вже бачив. І це спрацювало! Зараз відповідь має багато ясності і є прийнятою відповіддю, дякую
парламент

3
який хитрий план @парламент!
HankCa

Відповіді:


234

З документів:

detectChanges (): недійсний

Перевіряє детектор змін та його дітей.

Це означає, що якщо є випадок, коли будь-яка річ всередині вашої моделі (вашого класу) змінилася, але вона не відобразила вигляд, можливо, вам доведеться сповістити Angular, щоб виявити ці зміни (виявити локальні зміни) та оновити вигляд.

Можливими сценаріями можуть бути:

1- Детектор змін відірваний від подання (див. Розділ )

2- Оновлення трапилось, але воно не було у кутовій зоні, тому 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- Помістіть код усередині a 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, щоб перевірити та оновити вигляд (позначитиForCheck);

Тож якщо ви зробили це:

  obj.name = "a new name";

Вам потрібно зробити це:

  this.cd.markForCheck();

Швидше, нижче це призведе до запуску виявлення змін:

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

Що повністю замінило попередній obj на новий {};

2- Подія запустилася, як-от клацання чи якась така річ, або будь-який з дочірніх компонентів випустив подію.

Такі події, як:

  • Клацніть
  • Клавіатура
  • Події підписки
  • тощо.

Отже коротко:

  • Використовуйте, detectChanges()коли ви оновлювали модель після запуску кутової, вона виявляє зміни або якщо оновлення взагалі не було у світі кутів.

  • Використовуйте, markForCheck()якщо ви використовуєте OnPush, і ви обминаєте його ChangeDetectionStrategyшляхом мутації деяких даних або ви оновили модель всередині setTimeout ;


6
Тож якщо ви мутуєте цей об'єкт, подання не буде оновлено, і навіть якщо ви запустите detectChanges, це не спрацює, оскільки не було змін - це неправда. detectChangesоновлення перегляду. Дивіться це поглиблене пояснення .
Макс Корецький

Щодо markForCheck у висновку, він також не точний. Ось модифікований приклад із цього питання , він не виявляє зміни об’єктів за допомогою OnPush та markForCheck. Але той же приклад буде працювати, якщо немає стратегії OnPush.
колба Естуса

@Maximus, Що стосується вашого першого коментаря, я прочитав ваш пост, дякую, що це було добре. Але у своєму поясненні ви говорите, що якщо стратегія OnPush, означає, що якщо this.cdMode === ChangeDetectorStatus.Checkedвона не буде оновлювати перегляд, саме тому ви б використовували markForCheck.
Мілад

А щодо ваших посилань на plunker, обидва приклади для мене добре працюють, я не знаю, що ви маєте на увазі
Мілад

@Milad, ці коментарі надійшли від @estus :). Моя була о detectChanges. А cdModeв Angular немає 4.x.x. Про це я пишу у своїй статті. Радий, що вам сподобалось. Не забувайте, що ви можете порекомендувати його на середньому рівні або дотримуйтесь мене :)
Макс Корецький

99

Найбільша різниця між ними полягає в тому, що detectChanges()фактично запускає виявлення змін, в той час як markForCheck()не викликає виявлення змін.

Виявити Зміни

Цей використовується для запуску виявлення змін для дерева компонентів, починаючи з компонента, який ви запускаєте 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.detach()або за OnPushдопомогою стратегії виявлення змін. Усі власні обробники подій відзначають усі перегляди батьківських компонентів для перевірки.

Цей підхід часто використовується в ngDoCheckгачку життєвого циклу. Ви можете прочитати більше в розділі Якщо ви вважаєте, що ngDoCheckваш компонент перевіряється - прочитайте цю статтю .

Дивіться також Все, що вам потрібно знати про виявлення змін у Angular для отримання більш детальної інформації.


1
Чому detectChanges працює над компонентом та його дітьми, при цьому маркуючиForCheck на компоненті та предках?
пабло

@pablo, це за дизайном. Я не дуже знайомий з обґрунтуванням
Макс Корецький

@ AngularInDepth.com змінює виявлення інтерфейсу користувача, якщо є дуже інтенсивна обробка?
alt255

1
@jerry, рекомендований підхід полягає у використанні асинхронної труби, яка внутрішньо відстежує підписку і за кожним новим тригером значення markForCheck. Отже, якщо ви не використовуєте асинхронну трубку, це, мабуть, ви повинні використовувати. Однак майте на увазі, що оновлення магазину має відбутися внаслідок якогось події асинхронізації, щоб розпочати виявлення змін. Це завжди завжди так. Але є винятки blog.angularindepth.com/…
Макс Корецький

1
@MaxKoretskyiakaWizard дякую за відповідь. Так, оновлення магазину здебільшого є результатом вибору або встановленнямFetching раніше. і після отримання .. але ми не завжди можемо використовувати, async pipeоскільки всередині підписки у нас зазвичай є кілька справ, які можна зробити call setFromValues do some comparison.. і якщо asyncсам називає, markForCheckв чому проблема, якщо ми називаємо її самі? але знову ж таки, у нас зазвичай є 2-3, а іноді і більше селекторів для ngOnInitотримання різних даних ... і ми зателефонуємо markForCheckу всіх них ... це гаразд?
Джеррі

0

cd.detectChanges() запустить виявлення змін негайно з поточного компонента вниз через його нащадків.

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

  • Якщо ви хочете зменшити кількість разів виявлення змін, називається використання cd.markForCheck(). Часто зміни впливають на кілька компонентів і десь буде викликано виявлення змін. Ви по суті говорите: давайте просто переконаємось, що цей компонент також оновлюється, коли це відбувається. (Перегляд одразу оновлюється в кожному написаному нами проекті, але не в кожному тесті на одиницю).
  • Якщо ви не можете бути впевнені, що cd.detectChanges()в даний час не працює виявлення змін, використовуйте cd.markForCheck(). detectChanges()буде помилка в цьому випадку. Це, ймовірно, означає, що ви намагаєтесь відредагувати стан компонента предка, який працює проти припущень, що виявлення змін Angular розроблено навколо.
  • Якщо критично важливо, щоб перегляд оновлювався синхронно перед деякими іншими діями, використовуйте detectChanges(). markForCheck()може фактично не оновлювати перегляд у часі. Тестування блоку, що-небудь впливає на ваш погляд, наприклад, може вимагати дзвінка вручну, fixture.detectChanges()коли це не було необхідним у самому додатку.
  • Якщо ви змінюєте стан у компоненті з більшою кількістю предків, ніж нащадків, ви можете отримати підвищення продуктивності, використовуючи, detectChanges()оскільки ви не зайвим чином виявляєте зміни змін у предках компонента.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.