Spec-Zone.ru › Redux 3

Использование Immutable.JS с Redux

Содержание

  • Почему стоит использовать библиотеку, ориентированную на неизменяемость, такую как Immutable.JS?
  • Почему стоит выбрать Immutable.JS как библиотеку неизменяемых данных?
  • В чём недостатки использования Immutable.JS?
  • Стоит ли использовать Immutable.JS?
  • Какие рекомендуемые практики использования Immutable.JS с Redux?

Почему стоит использовать библиотеку, ориентированную на неизменяемость, такую как Immutable.JS?

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

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

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

Дополнительная информация

Документация

  • Рецепты: неизменяемость, побочные эффекты и мутации

Статьи

  • Вступление в Immutable.js и концепции функционального программирования
  • Плюсы и минусы использования неизменяемости с React.js

Почему стоит выбрать Immutable.JS как библиотеку неизменяемых данных?

Immutable.JS был разработан для обеспечения неизменяемости с высокой производительностью, чтобы преодолеть ограничения неизменяемости в JavaScript. Основные преимущества включают:

Гарантированная неизменяемость

Данные, заключённые в объект Immutable.JS, никогда не изменяются. Всегда возвращается новая копия. Это отличается от JavaScript, в котором некоторые операции не изменяют ваши данные (например, некоторые методы массивов, включая map, filter, concat, forEach и т. д.), но некоторые изменяют (Array’s pop, push, splice и т. д.).

Богатый API

Immutable.JS предоставляет богатый набор неизменяемых объектов для хранения ваших данных (например, Maps, Lists, Sets, Records и т. д.) и обширный набор методов для их обработки, включая методы для сортировки, фильтрации и группировки данных, их обратного порядка, уплощения и создания подмножеств.

Производительность

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

Immutable.JS избегает этого, умно используя общие структуры данных под капотом, минимизируя необходимость копирования данных. Это также позволяет выполнять сложные цепочки операций без создания ненужных (и дорогостоящих) промежуточных данных, которые быстро будут удалены.

Конечно, вы этого не видите — данные, которые вы передаёте объекту Immutable.JS, никогда не изменяются. Скорее, это промежуточные данные, созданные внутри Immutable.JS из цепочки вызовов методов, которые могут быть изменены. Таким образом, вы получаете все преимущества неизменяемых структур данных без (или с очень небольшим) негативным влиянием на производительность.

Дополнительная информация

Статьи

  • Immutable.js, постоянные структуры данных и совместное использование структур
  • PDF: Неизменяемость JavaScript - не изменяйте

Библиотеки

  • Immutable.js, постоянные структуры данных и совместное использование структур

В чём недостатки использования Immutable.JS?

Несмотря на мощь, Immutable.JS необходимо использовать с осторожностью, поскольку он имеет свои недостатки. Однако все эти проблемы можно легко преодолеть при тщательном программировании.

Сложность взаимодействия

JavaScript не предоставляет неизменяемые структуры данных. Таким образом, чтобы Immutable.JS обеспечивал свои гарантии неизменяемости, ваши данные должны быть заключены в объект Immutable.JS (такой как Map или List, и т. д.). Как только данные заключены таким образом, взаимодействовать с ними с обычными JavaScript-объектами становится сложно.

Например, вы больше не сможете ссылаться на свойства объекта через стандартную нотацию JavaScript точкой или квадратными скобками. Вместо этого вы должны ссылаться на них через методы Immutable.JS’s get() или getIn(), которые используют неудобный синтаксис, обращаясь к свойствам через массив строк, каждая из которых представляет собой ключ свойства.

Например, вместо myObj.prop1.prop2.prop3, вы бы использовали myImmutableMap.getIn([‘prop1’, ‘prop2’, ‘prop3’]).

Это затрудняет взаимодействие не только с вашим собственным кодом, но и с другими библиотеками, такими как lodash или ramda, которые ожидают обычные JavaScript-объекты.

Обратите внимание, что объекты Immutable.JS имеют метод toJS(), который возвращает данные как обычную структуру данных JavaScript, но этот метод очень медленный, и его широкое использование уничтожит преимущества производительности, которые предоставляет Immutable.JS.

После использования Immutable.JS он распространится по всему вашему коду

После того, как вы заключили свои данные в Immutable.JS, для доступа к ним вам нужно использовать средства доступа к свойствам get() или getIn() Immutable.JS.

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

Эту проблему можно избежать, отделив логику вашего приложения от структур данных, как описано в разделе «Рекомендованные практики» ниже.

Нет деструктуризации или операторов spread

Поскольку вам нужно получать доступ к данным через собственные методы Immutable.JS get() и getIn(), вы больше не можете использовать оператор деструктуризации JavaScript (или предлагаемый оператор spread объектов), что делает ваш код более громоздким.

Не подходит для небольших значений, которые часто изменяются

Immutable.JS лучше всего подходит для наборов данных, и чем больше, тем лучше. Он может быть медленным, когда ваши данные состоят из множества маленьких, простых JavaScript-объектов, каждый из которых содержит несколько ключей примитивных значений.

Однако это не относится к дереву состояния Redux, которое (обычно) представляет собой большой набор данных.

Сложная отладка

Объекты Immutable.JS, такие как Map, List, и т. д., могут быть сложными в отладке, так как при проверке такого объекта будет показана вся вложенная иерархия свойств, специфичных для Immutable.JS, которые вас не интересуют, в то время как ваши реальные данные, которые вас интересуют, скрыты на несколько уровней глубже.

Для решения этой проблемы используйте расширение браузера, такое как форматер объектов Immutable.js, который отображает ваши данные в Chrome Dev Tools и скрывает свойства Immutable.JS при проверке ваших данных.

Разрывает ссылки на объекты, что приводит к плохой производительности

Одно из ключевых преимуществ неизменяемости заключается в возможности проверки равенства по ссылке, что значительно улучшает производительность.

Если две разные переменные ссылаются на один и тот же неизменяемый объект, то простая проверка равенства этих двух переменных достаточно для определения того, что они равны и что объект, на который они оба ссылаются, не изменён. Проверка равенства никогда не должна проверять значения свойств объекта, так как он, конечно, неизменяем.

Однако проверка по ссылке не сработает, если ваши данные, заключённые в объект Immutable.JS, сами являются объектами. Это потому, что метод toJS() Immutable.JS, возвращающий данные, заключённые в объект Immutable.JS, как значение JavaScript, создаёт новый объект каждый раз, когда он вызывается, и таким образом разрывает ссылку с заключёнными данными.

Соответственно, вызов toJS() дважды, например, и присвоение результата двум различным переменным, приведёт к тому, что проверка равенства этих двух переменных завершится неудачей, даже если сами значения объекта не изменились.

Это является особенно важной проблемой, если вы используете toJS() в функции mapStateToProps компонента с оболочкой, так как React-Redux сравнивает каждый элемент в возвращаемом объекте props поверхностно. Например, значение, на которое ссылается свойство todos , возвращаемое mapStateToProps ниже, всегда будет другим объектом и, следовательно, не пройдёт проверку поверхностного равенства.

// AVOID .toJS() in mapStateToProps
function mapStateToProps(state) {
  return {
    todos: state.get('todos').toJS() // Always a new object
  }
}

При неудачной проверке поверхностного равенства React-Redux заставит компонент перерисоваться. Использование toJS() в mapStateToProps таким образом всегда приведет к перерисовке компонента, даже если значение никогда не меняется, что значительно сказывается на производительности.

Этому можно предотвратить, используя toJS() в компоненте высшего порядка, как обсуждается в разделе «Рекомендованные практики» ниже.

Дополнительная информация

Статьи

  • Immutable.js, постоянные структуры данных и совместное использование структуры
  • Неизменяемые структуры данных и JavaScript
  • React.js: антипаттерн чистого рендеринга и производительность
  • Создание эффективных пользовательских интерфейсов с React и Redux

Расширение Chrome

  • Форматировщик неизменяемых объектов

Стоит ли использовать Immutable.JS?

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

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

Эта проблема вызвана в основном возвращением изменённого объекта состояния из редуктора Redux. С Immutable.JS эта проблема просто не существует, тем самым удаляя целый класс ошибок из вашего приложения.

Это, наряду с его производительностью и богатым API для обработки данных, объясняет, почему Immutable.JS стоит усилий.

Дополнительная информация

Документация

  • Отладка: ничего не происходит при отправке действия

Какие лучшие практики использования Immutable.JS с Redux?

Immutable.JS может значительно повысить надёжность и производительность вашего приложения, но его нужно использовать правильно. Если вы решите использовать Immutable.JS (и помните, что вы не обязаны этого делать, и есть другие библиотеки для неизменяемости), следуйте этим рекомендациям, и вы сможете извлечь из него максимальную пользу, не сталкиваясь с возможными проблемами.

Никогда не смешивайте обычные объекты JavaScript с Immutable.JS

Никогда не допускайте, чтобы обычный объект JavaScript содержал свойства Immutable.JS. Аналогично, никогда не допускайте, чтобы объект Immutable.JS содержал обычный объект JavaScript.

Дополнительная информация

Статьи

  • Неизменяемые структуры данных и JavaScript

Сделайте всё дерево состояния Redux объектом Immutable.JS

Для приложения Redux всё дерево состояния должно быть объектом Immutable.JS, без использования обычных объектов JavaScript.

  • Создайте дерево, используя функцию fromJS() Immutable.JS.

  • Используйте версию combineReducers , совместимую с Immutable.JS, например, из redux-immutable, так как само Redux ожидает, что дерево состояния будет обычным объектом JavaScript.

  • При добавлении JavaScript-объектов в Immutable.JS Map или List с помощью методов Immutable.JS update, merge или set, убедитесь, что объект, который вы добавляете, сначала преобразован в неизменяемый объект с помощью fromJS().

Пример

// avoid
const newObj = { key: value }
const newState = state.setIn(['prop1'], newObj)
// newObj has been added as a plain JavaScript object, NOT as an Immutable.JS Map

// recommended
const newObj = { key: value }
const newState = state.setIn(['prop1'], fromJS(newObj))
// newObj is now an Immutable.JS Map

Дополнительная информация

Статьи

  • Неизменяемые структуры данных и JavaScript

Библиотеки

  • redux-immutable

Используйте Immutable.JS везде, кроме тупых компонентов

Использование Immutable.JS повсюду сохраняет производительность вашего кода. Используйте его в ваших умных компонентах, селекторах, сагах или thunk, создателях действий и, особенно, в редукторах.

Однако не используйте Immutable.JS в ваших тупых компонентах.

Дополнительная информация

Статьи

  • Неизменяемые структуры данных и JavaScript
  • Умные и тупые компоненты в React

Ограничьте использование toJS()

toJS() — это дорогостоящая функция, и она сводит на нет цель использования Immutable.JS. Избегайте её использования.

Дополнительная информация

Обсуждения

  • Lee Byron в Twitter: «Совет по производительности для #immutablejs…»

Селекторы должны возвращать объекты Immutable.JS

Всегда.

Используйте объекты Immutable.JS в ваших умных компонентах

Умные компоненты, которые обращаются к хранилищу через функцию React Redux connect, должны использовать возвращаемые селекторами значения Immutable.JS. Убедитесь, что вы избегаете потенциальных проблем с ненужным перерендерингом компонентов. Кэшируйте селекторы с помощью библиотеки, такой как reselect, если это необходимо.

Дополнительная информация

Документация

  • Рецепты: вычисление производных данных
  • FAQ: Неизменяемость данных
  • Документация Reselect: Как использовать Reselect с Immutable.js?

Статьи

  • Шаблоны и антипаттерны Redux

Библиотеки

  • Reselect: Библиотека селекторов для Redux

Никогда не используйте toJS() в mapStateToProps

Преобразование объекта Immutable.JS в объект JavaScript с помощью toJS() будет возвращать новый объект каждый раз. Если вы сделаете это в mapStateToProps, вы заставите компонент полагать, что объект изменился каждый раз, когда изменяется дерево состояния, и, таким образом, вызовете ненужный перерендеринг.

Дополнительная информация

Документация

  • FAQ: Неизменяемость данных

Никогда не используйте Immutable.JS в ваших тупых компонентах

Ваши тупые компоненты должны быть чистыми; то есть, они должны выдавать тот же результат при одинаковом входе и не иметь внешних зависимостей. Если вы передаёте такому компоненту объект Immutable.JS в качестве свойства, вы делаете его зависимым от Immutable.JS для извлечения значения свойства и иных манипуляций.

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

Дополнительная информация

Статьи

  • Неизменяемые структуры данных и JavaScript
  • Умные и тупые компоненты в React
  • Советы по созданию лучшей архитектуры Redux: уроки для масштабируемых приложений

Используйте компонент высшего порядка для преобразования свойств Immutable.JS ваших умных компонентов в свойства JavaScript ваших тупых компонентов

Что-то должно сопоставить свойства Immutable.JS в вашем умном компоненте с чистыми свойствами JavaScript, используемыми в вашем тупом компоненте. Это компонент высшего порядка (HOC), который просто принимает свойства Immutable.JS от вашего умного компонента и преобразует их с помощью toJS() в обычные свойства JavaScript, которые затем передаются вашему тупому компоненту.

Вот пример такого HOC:

import React from 'react'
import { Iterable } from 'immutable'

export const toJS = WrappedComponent => wrappedComponentProps => {
  const KEY = 0
  const VALUE = 1

  const propsJS = Object.entries(
    wrappedComponentProps
  ).reduce((newProps, wrappedComponentProp) => {
    newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(
      wrappedComponentProp[VALUE]
    )
      ? wrappedComponentProp[VALUE].toJS()
      : wrappedComponentProp[VALUE]
    return newProps
  }, {})

  return <WrappedComponent {...propsJS} />
}

И вот как вы его используете в вашем умном компоненте:

import { connect } from 'react-redux'

import { toJS } from './to-js'
import DumbComponent from './dumb.component'

const mapStateToProps = state => {
  return {
    // obj is an Immutable object in Smart Component, but it’s converted to a plain
    // JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
    // object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
    // there is no issue with errant re-renderings.
    obj: getImmutableObjectFromStateTree(state)
  }
}
export default connect(mapStateToProps)(toJS(DumbComponent))

Преобразованием объектов Immutable.JS в простые значения JavaScript внутри HOC мы достигаем портативности тупых компонентов, но без потерь производительности при использовании toJS() в умном компоненте.

Примечание: если ваше приложение требует высокой производительности, вам может понадобиться полностью избежать toJS(), и, таким образом, вам придётся использовать Immutable.JS в тупых компонентах. Однако, для большинства приложений это не так, и преимущества сохранения Immutable.JS вне тупых компонентов (сохранение, портативность и более лёгкое тестирование) будут значительно перевешивать любые предполагаемые улучшения производительности от его сохранения в них.

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

Дополнительная информация

Документация

  • React: Компоненты высшего порядка

Статьи

  • React Компоненты высшего порядка (в деталях)

Обсуждения

  • Reddit: комментарии acemarke и cpsubrian к Dan Abramov: Redux — это не архитектура или шаблон проектирования, это просто библиотека.

Gists

  • cpsubrian: Декораторы React для redux/react-router/immutable ‘умных’ компонентов

Используйте расширение Chrome Immutable Object Formatter для отладки

Установите Форматировщик неизменяемых объектов, и просмотрите данные Immutable.JS без лишних свойств самих объектов Immutable.JS.

Дополнительная информация

Расширение Chrome

  • Форматировщик неизменяемых объектов

© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/recipes/UsingImmutableJS.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API