В чем разница между markForCheck() и detectChanges()
В чем разница между ChangeDetectorRef.markForCheck()
а также ChangeDetectorRef.detectChanges()
?
Я только нашел информацию на SO о разнице между NgZone.run()
, но не между этими двумя функциями.
Ответы, содержащие только ссылку на документ, иллюстрируют некоторые практические сценарии выбора одного из других.
5 ответов
Из документов:
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;
Самое большое различие между ними состоит в том, что 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 для получения более подробной информации.
Я создал 4-минутный скринкаст, чтобы объяснить разницу между markForCheck() и detectChanges() - https://www.youtube.com/watch?v=OcphK_aEd7I
cd.detectChanges()
немедленно запустит обнаружение изменений от текущего компонента до его потомков.
cd.markForCheck()
не будет запускать обнаружение изменений, но пометит своих предков как требующих запуска обнаружения изменений. В следующий раз, когда обнаружение изменений запускается где угодно, оно будет запускаться также для тех компонентов, которые были отмечены.
- Если вы хотите уменьшить количество раз, когда обнаружение изменений называется, используйте
cd.markForCheck()
. Часто изменения затрагивают несколько компонентов, и где-то вызывается обнаружение изменений. По сути, вы говорите: давайте просто убедитесь, что этот компонент также обновится, когда это произойдет. (Представление немедленно обновляется в каждом проекте, который я написал, но не в каждом модульном тесте). - Если вы не уверены, что
cd.detectChanges()
в настоящее время невыполняется обнаружение изменений, используйтеcd.markForCheck()
.detectChanges()
в этом случае будет ошибка. Это, вероятно, означает, что вы пытаетесь отредактировать состояние компонента-предка, который работает против предположений, на основе которых построено обнаружение изменений Angular. - Если критически важно, чтобы представление обновлялось синхронно перед другим действием, используйте
detectChanges()
.markForCheck()
может не обновлять ваше представление вовремя. Модульное тестирование, что-то влияет на ваше представление, например, может потребовать ручного вызоваfixture.detectChanges()
когда это не было необходимо в самом приложении. - Если вы изменяете состояние в компоненте с большим количеством предков, чем потомков, вы можете повысить производительность, используя
detectChanges()
поскольку вы не запускаете без необходимости обнаружение изменений в предках компонента.
Самая большая разница заключается в том, что markForCheck будет проверять привязку текущего компонента и не вызывать какой-либо хук жизненного цикла, такой как DoCheck() в дочернем представлении, но обнаруживаетChange()!