Связанная информация
- Оригинальная ссылка:Angular Change Detection Explained
- Автор оригинала: Паскаль Прехт
- Переводчик: Цзя Вэнь
Примечание переводчика: эта статья представляет собой выступление автора на NG-GL (я не знаю, что это такое), которое он сам скомпилировал в расшифровку. Некоторые важные термины не переводятся без необходимости.
Ниже разделительной линии находится исходный текст.
содержание
- Что такое обнаружение изменений?
- Что вызвало изменение?
- Кто уведомляет Angular, когда происходит изменение?
- обнаружение изменений
- представление
- Более интеллектуальное обнаружение изменений
- Неизменяемые объекты
- Уменьшить количество проверок
- Observables
- Более..
Что такое обнаружение изменений
Основная задача обнаружения изменений состоит в том,Получить внутреннее состояние программы и сделать его видимым в пользовательском интерфейсе. этоусловиеМожет быть любым объектом, массивом, примитивным типом данных, т.е. любой структурой данных JavaScript.
этоусловиеВ конечном итоге это могут быть абзацы, таблицы, ссылки или кнопки в пользовательском интерфейсе и, особенно в Интернете, в DOM. Таким образом, в основном мы берем структуру данных в качестве входных данных, генерируем DOM в качестве выходных данных и показываем их пользователю. Мы называем этот процессrendering
(рендеринг)
Есть много решений этой проблемы. Например, один из способов — просто отправить http-запрос и повторно отобразить всю страницу. Другим методом является концепция Virtual Dom, предложенная ReactJs, которая заключается в обнаружении разницы между новым состоянием DOM и старым состоянием и отображении разницы.
Теро написал отличную статью оChange and its detection in JavaScript frameworks, то есть обнаружение изменений между разными JavaScript-фреймворками, если вам интересна эта проблема, предлагаю взглянуть. В этой статье я сосредоточусь на версиях Angular >= 2.x.
Что вызвало изменение?
Теперь, когда мы знаем, что такое обнаружение изменений, мы можем задаться вопросом: когда именно такое изменение происходит? Когда Angular узнает, что ему нужно обновить представление? Что ж, давайте посмотрим на код ниже:
@Component({
template: `
<h1>{{firstname}} {{lastname}}</h1>
<button (click)="changeName()">Change name</button>
`
})
class MyApp {
firstname:string = 'Pascal';
lastname:string = 'Precht';
changeName() {
this.firstname = 'Brad';
this.lastname = 'Green';
}
}
Если вы впервые смотрите на компоненты Angular, вы можете сначала проверить это.Как написать компонент вкладок
Приведенный выше компонент просто отображает два свойства и предоставляет метод, который вызывается при нажатии кнопки для изменения двух свойств. При нажатии кнопки программаусловиеПроизошло изменение, поскольку оно изменило свойства этого компонента. Это когда нам нужно обновить представление.
Вот еще один пример:
@Component()
class ContactsApp implements OnInit{
contacts:Contact[] = [];
constructor(private http: Http) {}
ngOnInit() {
this.http.get('/contacts')
.map(res => res.json())
.subscribe(contacts => this.contacts = contacts);
}
}
Этот компонент хранит список контактов и при инициализации делает http-запрос. Как только запрос вернется, список контактов будет обновлен. На данный момент состояние нашей программы изменилось, поэтому нам нужно обновить представление.
Из приведенных выше двух примеров мы видим, что в основном есть три причины изменения состояния программы:
-
мероприятие -
click
,submit
... - XHR- Получить данные с сервера.
-
Timers -
setTimeout()
,setInterval()
Все они асинхронные. Из этого можно сделать вывод, что пока происходит асинхронная операция, состояние нашей программы может измениться.Это когда Angular нужно уведомить об обновлении представления..
Кто информирует Angular?
До сих пор мы видели, что вызывает изменение состояния программы, но кто на самом деле информирует Angular, когда это представление должно измениться?
Angular позволяет нам напрямую использовать нативные API. Без вызова какого-либо метода Angular уведомляется об обновлении DOM. Это магия?
Если вы читали наши последние статьи, то знаете, что Zonesсделал все это. На самом деле в Angular есть собственная зона, которая называетсяNgZone
, мы написали об этом статью«Зоны в Angular», Вы также можете взглянуть.
Краткое описание: где-то в исходном коде Angular есть нечто, называемоеApplicationRef
, который слушаетNgZones
изonTurnDone
мероприятие. Пока это событие происходит, оно выполняетсяtick()
функция, эта функция выполняетобнаружение изменений.
// 真实源码的非常简化版本。
class ApplicationRef {
changeDetectorRefs:ChangeDetectorRef[] = [];
constructor(private zone: NgZone) {
this.zone.onTurnDone
.subscribe(() => this.zone.run(() => this.tick());
}
tick() {
this.changeDetectorRefs
.forEach((ref) => ref.detectChanges());
}
}
обнаружение изменений
Отлично, теперь мы знаем, когда срабатывает обнаружение изменений (срабатывает), но как это выполняется? Ну, первое, что нам нужно заметить, это то, что в Angular,Каждый компонент имеет свой детектор изменений
Это очевидно, потому что это дает нам индивидуальный контроль над тем, когда и как происходит обнаружение изменений для каждого компонента. Мы подробнее остановимся на этом позже.Предположим, что где-то в дереве компонентов произошло событие, например, нажатие кнопки. Что будет дальше? мы только что узнали,zones
выполнить данноеhandler
(обработчик событий) и уведомить Angular о завершении выполнения, затем Angular выполнит обнаружение изменений.
Причина, по которой данные всегда передаются сверху вниз, заключается в том, что для каждого компонента обнаружение изменений всегда выполняется сверху, каждый раз из корневого компонента. Это здорово, потому что однонаправленный поток данных более предсказуем, чем циклический поток данных. Мы всегда знаем, откуда берутся данные, используемые в представлении, потому что они могут происходить только из компонента, в котором они находятся.
Еще одно интересное наблюдение заключается в том, что обнаружение изменений более стабильно за один проход. Это означает, что если один из компонентов вызовет какие-либо побочные эффекты после того, как мы запустим обнаружение изменений в первый раз, Angular выдаст ошибку.
представление
По умолчанию Angular работает очень быстро, когда происходит событие, хотя мы каждый раз проверяем каждый компонент, он выполняет тысячи проверок за миллисекунды. В основном это связано с тем, что Angular генерирует дружественный к VM код,
Что это значит? На самом деле, когда мы говорим, что у каждого компонента есть свой детектор изменений, мы на самом деле не имеем в виду, что в Angular есть такая общая вещь (генетическая вещь), которая отвечает за обнаружение изменений каждого компонента.
Причина этого в том, что он (детектор изменений) должен быть написан динамическим, чтобы он мог обнаруживать все компоненты независимо от структуры модели этого компонента. И виртуальным машинам не нравится такой динамический код, потому что виртуальные машины не могут его оптимизировать. Когда структура объекта не всегда одинакова, ее часто называют полиморфной.
Angular генерирует классы детекторов изменений во время выполнения для каждого компонента, и эти классы детекторов изменений являются мономорфными, поскольку они точно знают, как выглядит модель компонента. Виртуальные машины могут идеально оптимизировать этот код, благодаря чему он выполняется очень быстро. Хорошая новость заключается в том, что нам не нужно об этом беспокоиться, потому что Angular делает это автоматически.
См. Виктора Савкина.Change Detection Reinventedвы можете получить более подробное объяснение.
Более интеллектуальное обнаружение изменений
Мы знаем, что как только событие происходит, Angular должен обнаруживать каждый раз, когдавсекомпонента, потому что состояние приложения могло измениться. Но если мы позволим только Angularчасть, где состояние изменилосьНе было бы неплохо провести тест проверки изменений?
Да, это красиво, и мы можем это сделать. Просто передайте следующие структуры данных -Immutables
а такжеObservables
Если бы нам довелось использовать эти структуры данных и мы сказали об этом Angular, обнаружение изменений было бы намного быстрее. Это здорово, так как именно?
Понимание изменчивости
Чтобы понять, почему и как неизменяемые структуры данных способствуют более быстрому обнаружению изменений, нам нужно понять, что такое изменчивость на самом деле. Предположим, у нас есть следующие компоненты:
@Component({
template: '<v-card [vData]="vData"></v-card>'
})
class VCardApp {
constructor() {
this.vData = {
name: 'Christoph Burgdorf',
email: 'christoph@thoughtram.io'
}
}
changeData() {
this.vData.name = 'Pascal Precht';
}
}
VCardApp
использовать<v-card>
Как дочерний компонент, дочерний компонент имеет свойство вводаvData
,мы будемVCardApp
свойстваvData
Передайте дочерние компоненты.vData
объект с двумя свойствами. Еще одинchangeData()
метод, этот метод меняетсяvData
изname
. Никакой особой магии здесь нет.
Важная часть здесь заключается в том, чтоchangeData()
путем изменения егоname
свойство измененоvData
, хотя это свойство будет изменено,ноvData
Ссылки не изменились.
Предположим, какое-то событие вызываетchangeData()
выполняется, как будет выполняться обнаружение изменений? Сначала изменяется vData.name, затем передается<v-card>
. <v-card>
Детектор изменений начинает обнаруживать входящиеvData
Независимо от того, нет никаких изменений, ответ да, без изменений. Потому что ссылка на этот объект не был изменен. Однако его свойство имени было изменено, поэтому даже тогда угловые будут выполнять обнаружение изменений для этого объекта (VDATA).
Поскольку объект в JavaScript является многотабличным (за исключением базового типа данных), Angular должен быть консервативным для каждого компонента каждый раз, когда происходит событие.
В этот момент,неизменяемая структура данныхможет пригодиться.
Неизменяемые объекты
Неизменяемые объекты гарантируют, что объект не может быть изменен. Это означает, что если мы используем неизменяемый объект и пытаемся изменить его, мы всегда будем получать новую ссылку, потому что исходный объект был неизменяемым.
Сократить количество проверок
Когда входное свойство не изменилось, Angular пропускает обнаружение изменений для всего поддерева. Мы только что сказали, что «изменение» означает «новая ссылка». Если мы используем неизменяемые объекты в программе Angular, все, что нам нужно сделать, это сообщить Angular, что этот компонент может пропустить обнаружение изменений, если входные данные не изменились.
Благодаря нашему исследованию<v-card>
Давайте посмотрим, как это работает:
@Component({
template: `
<h2>{{vData.name}}</h2>
<span>{{vData.email}}</span>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
@Input() vData;
}
можно увидеть,VCardCmp
Зависит только от входных свойств. отлично. Если ни одно из его входных свойств не изменилось, мы можем позволить Angular пропустить обнаружение изменений для этого поддерева, установив политику обнаружения изменений наOnPush
просто хорошо
@Component({
template: `
<h2>{{vData.name}}</h2>
<span>{{vData.email}}</span>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
@Input() vData;
}
Вот и все! Вы можете попробовать представить большее дерево компонентов, в котором мы можем пропустить все поддерево (обнаружения изменений), если мы используем неизменяемые объекты.
Jurgen Van MoereнаписалПодробные статьи, о том, как он написал быстрый тральщик, используя Angular и Immutablejs. Я рекомендую вам взглянуть.
Observables
Как упоминалось ранее, когда происходят измененияObservables
Это также дало нам гарантию. В отличие от неизменяемых объектов, когда происходят изменения.Observables
Новых ссылок нам не предоставляют. Вместо этого они запускают события, и позволяют нам зарегистрироваться, чтобы подписаться на эти события, чтобы реагировать на них.
Итак, если мы используем Observables и хотим использоватьOnPush
чтобы пропустить обнаружение изменений в поддереве, но ссылки на эти объекты никогда не меняются, что нам делать? На самом деле, для некоторых событий в Angular естьочень умнометод, чтобы сделать этот путь в дереве компонентов обнаруженным, и этот метод как раз то, что нам нужно.
Чтобы понять, что это значит, давайте посмотрим на следующий компонент:
@Component({
template: '{{counter}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {
@Input() addItemStream:Observable<any>;
counter = 0;
ngOnInit() {
this.addItemStream.subscribe(() => {
this.counter++; // 程序状态改变
})
}
}
Предположим, мы пишем интернет-магазин с корзиной для покупок. Мы хотим, чтобы на нашей странице отображался небольшой таймер, когда пользователь кладет товар в корзину, чтобы пользователь мог знать, сколько товаров в корзине.
CartBadgeCmp
Просто сделайте такую вещь. оно имеетcounter
В качестве входного свойства этоcounter
это поток событий, который запускается, когда товар добавляется в корзину.
я не будуObservables
Принцип работы описан слишком подробно, вы можете сначала прочитать эту статью«Использование Observables в Angular»
В дополнение к этому мы устанавливаем стратегию обнаружения изменений какOnPush
, поэтому обнаружение изменений выполняется не всегда, а только при изменении входных свойств компонента.
Однако, как упоминалось ранее,addItemStreem
Изменения никогда не происходят, поэтому в поддереве этого компонента никогда не происходит обнаружение изменений. Это неправильно, потому что компонент привязан к жизненному циклу.ngOnInit
зарегистрировал этот поток событий вcounter
увеличено. Это изменение состояния программы, и мы хотим, чтобы оно отражалось в нашем представлении, верно?
Ниже показано, как может выглядеть наше дерево обнаружения изменений (все компоненты настроены какOnPush
). При возникновении события обнаружение изменений не выполняется.
OnPush
, для этого компонента еще необходимо выполнить обнаружение изменений?
Не волнуйтесь, Angular учитывает это для нас. Как упоминалось ранее, обнаружение изменений всегда выполняется сверху вниз. тогда все, что нам нужно, это обнаружение, посколькукорневой компонентприбытькомпонент, в котором произошло изменениевесь путь. Angular не знает, какой именно, но мы знаем.
мы можем пройтивнедрение зависимостис помощью компонентаChangeDetectorRef
, с помощью которого вы можете использовать метод, называемыйmarkForCheck()
API. Это делает именно то, что нам нужно! Он отмечает весь путь от текущего компонента к корневому компоненту, и при следующем обнаружении изменений они будут обнаружены.
Мы внедряем его в наш компонент:
constructor(private cd: ChangeDetectorRef) {}
Затем скажите Angular отметить весь путь от этого компонента до корневого компонента для проверки:
ngOnInit() {
this.addItemStream.subscribe(() => {
this.counter++; // application state changed
this.cd.markForCheck(); // marks path
})
}
}
Бум, готово! На следующем изображении показано, как выглядит дерево компонентов, когда происходит наблюдаемое событие:
Теперь, когда выполняется обнаружение изменений,
Разве это не круто? После завершения обнаружения изменений он возвращается к полному восстановлению дерева.OnPush
условие.
Более
На самом деле, есть много API, которые не упомянуты в этой статье, и вы предоставите вам возможность копнуть глубже.
существуетэтот проектЕсть также несколько демоверсий, которые вы можете запустить на своем компьютере.
Надеюсь, эта статья даст вам представление о неизменяемых структурах данных, а такжеObservable
Как заставить наше приложение Angular работать быстрее, иметь более четкую картину.
Примечание переводчика
Переводчик также просмотрел некоторые ссылки в тексте в процессе перевода и глубже понял весь механизм обнаружения изменений Angular.Я хотел бы порекомендовать его здесь (может потребоваться научный доступ в Интернет).
- Change Detection Reinvented Victor SavkinСодержание этого видео похоже на содержание этой статьи, и оно более подробно для сравнения друг с другом.
- Change And Its Detection In JavaScript FrameworksВ этой статье рассказывается о различиях в реализации обнаружения изменений между различными JS-фреймворками, это просто обзор, вы также можете взглянуть. Стоит отметить, что Angular здесь — это AngularJs, то есть версия Angular1.x, версия Angular >= 2 отличается, пожалуйста, ознакомьтесь с зумами (см. ниже).
- Understanding zonesПредставлена зона.js.
- Zones in AngularОписывает, как Angular использует zone.js (путем расширения zone.js) для реализации обнаружения изменений.
Кроме того, автор обнаружил интересное явление. Иностранцы, которые ведут блоги, любят везде цитировать чужие блоги или выступления. Видите ли, в этой статье цитируется другая статья, а в другой статье цитируются другие. Это формирует дерево. Следует ли выполнять BFS или DFS на этом дереве? (То есть, если вы видите цитируемую статью, вы должны сначала прочитать цитируемую статью или сначала прочитать текущую статью, а затем прочитать цитируемую статью.) Мое предложение состоит в том, что если в статье упоминается, что эта статья основана на этой цитируемой статье , то конечно надо сначала прочитать ее, иначе сначала прочитать текущую статью.