AngularInDepth уходит от Medium. Более свежие статьи размещаются на новой платформе inDepth.dev. Спасибо за то, что участвуете в глубоком движении!

Представьте, что у нас есть рекурсивная структура данных в магазине, скажем, информация о категории продукта в приложении электронной коммерции. Категория - это классификация, к какому типу продукта это относится. Например, в категории Мобильные телефоны могут быть подкатегории, такие как Google, Apple, Samsung и т. Д., И каждая подкатегория может, в свою очередь, иметь дополнительные подкатегории - подкатегория Google может иметь дополнительные подкатегории - Google Pixel, Google Pixel XL, Google Pixel 2 и Google Pixel 2 XL . Эти подкатегории будут того же типа, что и их родительские категории - ICategory. Это называется рекурсивным типом данных.

Информация о категории Мобильные телефоны доступна в API, и когда мы ее извлекаем, мы сохраняем ответ в магазине. Допустим, мы хотим получить информацию о категории Google Pixel.

Вместо того, чтобы сделать еще один вызов API, мы можем проверить, есть ли у нас информация уже в магазине, если есть, мы получаем ее оттуда, и если у нас нет необходимой информации в магазине, мы сделаем вызов API для возьми.

Зачем нам нормализовать состояние?

Глядя на рекурсивную структуру данных, проверка того, есть ли у нас информация, не совсем проста. Мы все еще можем это сделать, но нам понадобится какой-то механизм цикла или алгоритм двоичного дерева, чтобы просмотреть различные категории в магазине и посмотреть, есть ли в нем Google Pixel.

Вместо этого мы могли бы сгладить структуру категорий, сопоставить категории с идентификаторами категорий и сохранить их в магазине. Таким образом, будет легче найти данные, которые уже есть в магазине. Этот процесс выравнивания данных называется нормализацией. Когда мы извлекаем информацию из хранилища, мы выполняем обратную нормализацию, то есть берем сглаженные данные и возвращаем их во вложенный объект. Это называется денормализацией.

Преимущества:

  • Уменьшение использования памяти приложения.
  • Легче работать с вложенными объектами, чтобы получить вложенный объект и быстрее получить доступ
  • Никакого неприятного кода для поиска значений во вложенных объектах

Другие причины, упомянутые в документации по redux.js:

  • Поскольку каждый элемент определен только в одном месте, нам не нужно пытаться вносить изменения в нескольких местах, если этот элемент обновляется.
  • Логика редуктора не связана с глубокими уровнями вложенности, поэтому она, вероятно, будет намного проще.
  • Логика получения или обновления данного элемента теперь довольно проста и согласована. Зная тип элемента и его идентификатор, мы можем найти его напрямую, выполнив пару простых шагов, без необходимости рыться в других объектах, чтобы найти его.
  • Поскольку каждый тип данных разделен, обновление, такое как изменение текста комментария, потребует только новых копий части дерева «comments› byId ›comment». Обычно это означает меньшее количество частей пользовательского интерфейса, которые необходимо обновить, поскольку их данные были изменены. Напротив, обновление комментария в исходной вложенной форме потребовало бы обновления объекта комментария, родительского объекта сообщения, массива всех объектов сообщения и, вероятно, вызвало бы все компоненты сообщения и компоненты комментария. в пользовательском интерфейсе для повторного рендеринга.

normalizr

Normalizr от Paul Armstrong - хорошая библиотека для достижения того, что мы здесь хотим - она ​​может преобразовывать глубоко вложенные объекты в плоскую структуру и наоборот.

В приложении Angular @ngrx нормализацию лучше выполнять в эффектах, прежде чем мы отправим действие для обновления хранилища, и денормализацию в селекторе, прежде чем мы предоставим значение из магазин своим потребителям.

Процесс будет состоять из следующих этапов:

Нормализация:

  • Действие запускается, чтобы получить подробную информацию о категории.
  • Эффект прослушивает действие и проверяет, присутствует ли информация в магазине, используя селекторы для категории.
  • Если информация доступна в магазине, мы отправляем действие с деталями категории из магазина, возвращаемыми селектором, и завершаем эффект.
  • Если информация недоступна в магазине, будет выполнен вызов API для извлечения данных и их нормализации (удалите все дочерние категории из ответа и сгладьте их)
  • Эффект запускает действие для обновления магазина, и эффект завершается.
  • Редуктор обновляет магазин информацией

Денормализация:

  • Запрос информации о категории происходит путем вызова селекторов
  • Селектор проверяет, есть ли категория в магазине
  • Если категория существует в магазине, мы денормализуем ее (заполняем все категории детей / внуков и т. Д.) Перед возвратом
  • Если категории не существует, мы возвращаемся, говоря, что у нас нет этой информации. (Этот случай будет обработан в эффекте, когда он проверяет, находится ли категория в магазине. Если нет, он выполняет вызов API для ее получения)

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

Нормализация:

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

С этой структурой схемы наши нормализованные категории будут объектом с именем Categories в нормализованном ответе, а подкатегории для каждой категории будут массивом идентификаторов подкатегорий .

Затем остается просто вызвать normalize с информацией о категории и схемой категории.

Если мы передадим в этот метод сведения о категории для мобильных телефонов, он создаст плоскую карту мобильных телефонов и всех его категорий и будет выглядеть так:

Эффект:

Теперь, когда нормализация готова, мы можем использовать ее в нашем эффекте для нормализации ответа API. Как описано ранее, мы проверяем, существует ли он в магазине, с помощью селектора. Если это так, мы возвращаем эту информацию; в противном случае мы выполняем вызов API, нормализуем ответ и отправляем действие для обновления магазина. Скоро мы перейдем к селектору.

Редуктор

В нашем редукторе мы перебираем нормализованные данные категории и сохраняем категорию и ее подкатегории в хранилище вместе с их идентификаторами категорий, стараясь не заменять все состояние нормализованными данными категории для запрошенной категории.

Это часть нормализации.

Денормализация:

Подпись для денормализации - denormalize(input, schema, entities).

input: (обязательно) нормализованный результат, который следует денормализовать.

schema: (обязательно) определение схемы, которое использовалось для получения значения для input.

entities: (обязательный) объект, привязанный к именам схемы сущностей, который может появиться в денормализованном выводе. Также принимает объект с неизменяемыми данными.

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

Селектор

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

Это вернет информацию о категории точно так же, как мы получили ее из API.

Добавление дополнительной информации к объекту

Скажем, API для получения сведений о категории имеет возможность возвращать информацию о категории с определенной глубиной. Например, если мы запросим мобильные телефоны с depth = 0, то мы получим только информацию о мобильных телефонах, но не информацию об их подкатегории. Для depth = 1 возвращается информация о своих подкатегориях, но не об их подкатегориях и т. Д.

Глубина для каждой категории различается, некоторые категории глубже, чем другие. Имеет ли категория подкатегории, можно определить по флагу - isLeaf. Если isLeaf истинно, у категории нет подкатегорий, а если ложно, у категории есть подкатегории.

Допустим, у нас в магазине уже есть мобильные телефоны уровня 1.

Когда запрашивается информация о Google с глубиной 0, мы можем вернуть ее из магазина, поскольку она уже существует. Но когда мы запрашиваем информацию о Google с глубиной 1, у нас нет необходимой информации, поскольку информация в магазине имеет только глубину 0 (не имеет подкатегорий).

В этом случае нам нужно сделать 2 вещи:

  1. Сделайте вызов API, чтобы получить дополнительную информацию
  2. Разместите информацию, которую мы получили от API, в магазине

Таким образом, мы запрашиваем только ту информацию, которой у нас нет, и не дублируем вызов API или магазин.

Но проблема в том, что атрибут depth не существует в ответе API. Чтобы оптимизировать вызовы API и магазин, нам необходимо знать глубину информации о каждой категории, которая есть в магазине.

processStrategy

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

Подпись для Entity - Entity(key, definition = {}, options = {}).

options могут принимать idAttribute, mergeStrategy и processStrategy.

  • idAttribute можно использовать для получения идентификатора из атрибута категории, если это не число.
  • mergeStrategy можно использовать при слиянии двух сущностей с одинаковым значением id.
  • processStrategy позволяет нам добавлять в объект дополнительные данные или значения по умолчанию.

Подпись для processStrategy - processStrategy(value, parent, key).

  • value: входное значение объекта.
  • parent: родительский объект входного массива.
  • key: ключ, по которому входной массив появляется в родительском объекте.

Во-первых, мы создаем интерфейс, который будет содержать как информацию о категории, так и глубину:

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

categorySchema со стратегией процесса теперь будет выглядеть так:

Теперь мы можем использовать categorySchema для нормализации данных, а способ вызова normalize normalizr - передать информацию о категории с запрошенной глубиной и categorySchema.

Нормализованная информация о категории теперь будет выглядеть так:

С помощью этой структуры, если мы хотим получить информацию о Google Pixel с определенной глубиной, достаточно просто заглянуть в нормализованную структуру данных, чтобы увидеть, не превышает ли глубина категории в магазине или равно требуемой глубине. Если да, верните информацию, в противном случае выполните вызов API.

В эффекте, который обрабатывает запрос на получение сведений о категории, мы сначала проверим магазин, чтобы увидеть, есть ли там запрошенные данные. В таком случае мы возвращаем эту информацию и не выполняем вызовы API. Если нет, то мы вызываем API, нормализуем ответ и отправляем действие для обновления магазина этой информацией.

Редуктор будет выглядеть, как показано ниже, и проверяет, что состояние категории не заменяется, а вместо этого обновляется новыми сведениями о категории.

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

Заключение

Лучше всего сгладить состояние, чтобы мы могли повысить производительность поиска и обновления приложения и упростить и упорядочить базу кода. Использование библиотеки normalizr кажется изящным решением для достижения этой цели, а также помогает нам сохранять код красивым и чистым. Для получения информации о других способах его использования обратитесь к документации для normalizr.

Спасибо за внимание! Если вам понравилась эта статья, пожалуйста, 👏 и помогите другим найти ее. Пожалуйста, не стесняйтесь делиться своими мыслями в разделе комментариев ниже. Следуйте за мной в Medium или Twitter, чтобы увидеть больше статей. Счастливых программистов !! 💻 ☕️