Обнаружение изменений Angular и стратегия OnPush
Опубликовано: 2022-03-11Вы начали использовать Angular для всех своих любимых проектов. Вы знаете, что может предложить Angular и как вы можете использовать его для создания потрясающих веб-приложений. Но есть определенные особенности Angular, и знание их поможет вам лучше использовать Angular для своих проектов.
Поток данных находится в центре почти всего, что связано с Angular, поэтому стоит знать об обнаружении изменений, так как это поможет вам гораздо проще отслеживать ошибки и даст вам возможность дополнительно оптимизировать ваши приложения при работе со сложным набором данных.
В этой статье вы узнаете, как Angular обнаруживает изменения в своих структурах данных и как вы можете сделать их неизменяемыми, чтобы максимально использовать стратегии обнаружения изменений Angular.
Обнаружение изменений в Angular
Когда вы изменяете любую из своих моделей, Angular обнаруживает изменения и немедленно обновляет представления. Это обнаружение изменений в Angular. Цель этого механизма — обеспечить постоянную синхронизацию базовых представлений с соответствующими моделями. Эта основная функция Angular — это то, что заставляет фреймворк работать и отчасти является причиной того, что Angular — отличный выбор для разработки современных веб-приложений.
Модель в Angular может измениться в результате любого из следующих сценариев:
События DOM (щелчок, наведение и т. д.)
AJAX-запросы
Таймеры (setTimer(), setInterval())
Детекторы изменений
Все приложения Angular состоят из иерархического дерева компонентов. Во время выполнения Angular создает отдельный класс детекторов изменений для каждого компонента в дереве, который затем в конечном итоге формирует иерархию детекторов изменений, аналогичную иерархическому дереву компонентов.
Всякий раз, когда запускается обнаружение изменений, Angular просматривает это дерево детекторов изменений, чтобы определить, сообщил ли какой-либо из них об изменениях.
Цикл обнаружения изменений всегда выполняется один раз для каждого обнаруженного изменения и начинается с детектора корневых изменений и продолжается последовательно вниз. Этот выбор последовательного дизайна хорош, потому что он обновляет модель предсказуемым образом, поскольку мы знаем, что данные компонента могут поступать только от его родителя.
Детекторы изменений позволяют отслеживать предыдущие и текущие состояния компонента, а также его структуру, чтобы сообщать об изменениях в Angular.
Если Angular получает отчет от детектора изменений, он дает указание соответствующему компоненту повторно отобразить и соответствующим образом обновить DOM.
Стратегии обнаружения изменений
Значение и ссылочные типы
Чтобы понять, что такое стратегия обнаружения изменений и почему она работает, мы должны сначала понять разницу между типами значений и ссылочными типами в JavaScript. Если вы уже знакомы с тем, как это работает, вы можете пропустить этот раздел.
Для начала давайте рассмотрим типы значений и ссылочные типы, а также их классификации.
Типы значений
логический
Нулевой
Неопределенный
Количество
Нить
Для простоты можно представить, что эти типы просто хранят свое значение в памяти стека (что технически неверно, но для этой статьи достаточно). Например, см. Память стека и ее значения на изображении ниже.
Ссылочные типы
Массивы
Объекты
Функции
Эти типы немного сложнее, поскольку они хранят ссылку в памяти стека, которая указывает на их фактическое значение в памяти кучи. Вы можете увидеть, как память стека и память кучи работают вместе в примере ниже. Мы видим, что память стека ссылается на фактические значения ссылочного типа в памяти кучи.
Важное различие между типами значений и ссылочными типами заключается в том, что для чтения значения типа значения нам просто нужно запросить память стека, но для чтения значения ссылочного типа нам нужно сначала запросите память стека, чтобы получить ссылку, а затем используйте эту ссылку, чтобы запросить память кучи, чтобы найти значение ссылочного типа.
Стратегия по умолчанию
Как мы уже говорили ранее, Angular отслеживает изменения в модели, чтобы убедиться, что он улавливает все изменения. Он проверит любые различия между предыдущим состоянием и текущим состоянием общей модели приложения.
Вопрос, который Angular задает в стратегии обнаружения изменений по умолчанию: изменилось ли какое-либо значение в модели? Но для ссылочного типа мы можем реализовать стратегии, чтобы задать лучший вопрос. Именно здесь вступает в действие стратегия обнаружения изменений OnPush.

Стратегия OnPush
Основная идея стратегии OnPush проявляется в осознании того, что если мы рассматриваем ссылочные типы как неизменяемые объекты, мы можем намного быстрее определить, изменилось ли значение. Когда ссылочный тип является неизменяемым, это означает, что каждый раз, когда он обновляется, ссылка в памяти стека должна изменяться. Теперь мы можем просто проверить: изменилась ли ссылка (в стеке) ссылочного типа? Если да, то только тогда проверяйте все значения (в куче). Вернитесь к предыдущим схемам кучи стека, если это сбивает с толку.
Стратегия OnPush задает два вопроса вместо одного. Изменилась ли ссылка ссылочного типа? Если да, то изменились ли значения в куче памяти?
Например, предположим, что у нас есть неизменяемый массив из 30 элементов, и мы хотим знать, есть ли какие-либо изменения. Мы знаем, что для того, чтобы были какие-либо обновления неизменяемого массива, ссылка на него (в стеке) должна была измениться. Это означает, что мы можем сначала проверить, отличается ли ссылка на массив, что потенциально избавит нас от выполнения дополнительных 30 проверок (в куче), чтобы определить, какой элемент отличается. Это называется стратегией OnPush.
Итак, вы можете спросить, что значит рассматривать ссылочные типы как неизменяемые? Это означает, что мы никогда не устанавливаем свойство ссылочного типа, а вместо этого переназначаем значение целиком. Смотри ниже:
Обработка объектов как изменяемых:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }
Обработка объектов как неизменяемых:
static mutable() { var before = {foo: "bar"}; var current = before; current = {foo "hello"}; console.log(before === current); // => false }
Обратите внимание, что в приведенных выше примерах мы «обрабатываем» ссылочные типы как неизменяемые по соглашению, поэтому, в конце концов, мы все еще работаем с изменяемыми объектами, но просто «притворяемся», что они неизменны.
Так как же реализовать стратегию OnPush для компонента? Все, что вам нужно сделать, это добавить параметр changeDetection
в их аннотацию @Component.
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }
Неизменяемый.js
Хорошая идея обеспечить неизменность, если вы решите использовать стратегию OnPush для компонента Angular. Вот тут-то и появляется Immutable.js.
Immutable.js — это библиотека, созданная Facebook для неизменности в JavaScript. У них много неизменяемых структур данных, таких как список, карта и стек. Для целей этой статьи будут проиллюстрированы список и карта. Для получения дополнительной информации, пожалуйста, ознакомьтесь с официальной документацией здесь.
Чтобы добавить Immutable.js в свои проекты, обязательно войдите в свой терминал и выполните:
$ npm install immutable --save
Также убедитесь, что структуры данных, которые вы используете, импортированы из Immutable.js в компонент, в котором вы их используете.
import {Map, List} from 'immutable';
Вот как можно использовать карту Immutable.js:
var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar
И массив можно использовать:
var helloWorld = ["Hello", "World!"]; var immutableHelloWorld = List(helloWorld); console.log(immutableHelloWorld.first()); // => Hello console.log(immutableHelloWorld.last()); // => World! helloWorld.push("Hello Mars!"); console.log(immutableHelloWorld.last()); // => Hello Mars!
Недостатки использования Immutable.js
Есть пара основных спорных недостатков использования Immutable.js.
Как вы могли заметить, использование его API немного громоздко, и традиционному разработчику JavaScript это может не понравиться. Более серьезная проблема связана с невозможностью реализовать интерфейсы для вашей модели данных, поскольку Immutable.js не поддерживает интерфейсы.
Заворачивать
Вы можете спросить, почему стратегия OnPush не является стратегией по умолчанию для Angular. Я предполагаю, что это потому, что Angular не хотел заставлять разработчиков JavaScript работать с неизменяемыми объектами. Но это не значит, что вам запрещено его использовать.
Если вы хотите использовать это в своем следующем веб-проекте, теперь вы знаете, как легко Angular позволяет переключиться на другую стратегию обнаружения изменений.