Попробуйте Immer, Redux Toolkit, Seamless или вообще без библиотеки

В предыдущем посте я сказал, что Immutable.js не следует использовать в качестве вспомогательной библиотеки неизменяемости для Redux, но не рекомендовал никаких других вариантов.

Вот несколько альтернатив (в произвольном порядке), которые мне нравились больше, чем Immutable.js:

  1. Бесшовная-неизменная
  2. Иммер
  3. Redux Toolkit
  4. Никакой библиотеки

Резюме: почему не Immutable.js?

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

Это заставляет библиотеку распространяться по всей кодовой базе, даже в те места, которые не должны заботиться о неизменности, такие как тупые компоненты React.

Это может излишне усложнить жизнь разработчикам, которые должны постоянно спрашивать, являются ли некоторые данные простым JavaScript или упакованы в Immutable.js.

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

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

Альтернатива 1: бесшовные-неизменяемые

Изначально бесшовность очень похожа на Immutable.js: все объекты должны быть обернуты неизменяемой фабричной функцией, а API библиотеки должен использоваться для внесения в них изменений. Ключевое отличие состоит в том, что бесшовные объекты можно читать с помощью обычного JavaScript.

Рассмотрим редюсер, который выглядит почти идентично для Seamless и Immutable.js:

Но есть разница в том, как todos передается компоненту через mapStateToProps Redux, и в том, как этот компонент затем использует эту опору - с Seamless это все можно сделать с помощью простого JavaScript.

Функции mapStateToProps, использующие Seamless и Immutable.js, выглядят так:

(state) => { todos: state.todos }  // Seamless
(state) => { todos: state.get('todos') }  // ImmutableJS

А использование опоры внутри компонента выглядит так:

props.todos.a.isComplete  // Seamless
props.todos.getIn(['a', 'isComplete']) // ImmutableJS

Таким образом, использование Seamless в большей степени ограничено базой кода, чем Immutable.js, потому что бесшовные объекты можно читать с помощью простого JavaScript.

Такие вещи, как глупые компоненты React и базовые селекторы (вместе со всеми их тестами), не будут показывать никаких признаков библиотеки. Однако изменение данных может быть выполнено только с использованием бесшовных API-интерфейсов, поэтому вполне вероятно, что в служебных функциях и более сложных селекторах библиотека по-прежнему будет видна.

Альтернатива 2: погружение

Immer работает совершенно иначе, чем Immutable.js и Seamless, и поэтому полностью решает проблему обратной совместимости - это простой JavaScript.

Во многом по этой причине Immer стал чрезвычайно популярным решением для управления неизменяемостью в Redux. Даже в Руководстве по стилю Redux он явно фаворит:

«Используйте Immer для написания неизменяемых обновлений - НАСТОЯТЕЛЬНО РЕКОМЕНДУЕТСЯ»

Несколько удивительно, но Immutable.js по-прежнему возглавляет чарты загрузки npm, но я подозреваю, что Immer скоро возьмет на себя инициативу.

Immer позволяет разработчикам писать операции изменения, но гарантирует, что они происходят только во временном «черновом» состоянии, которое позже переводится в новое состояние:

Использование Immer похоже на личного помощника; он берет письмо (текущее состояние) и дает вам копию (черновик) для внесения изменений. Как только вы закончите, помощник возьмет ваш черновик и создаст для вас настоящее неизменное окончательное письмо (следующее состояние). - Погружение

Это обеспечивается produce функцией Immer:

produce(currentState, producer: (draftState) => void): nextState

Передайте текущее состояние, желаемое обновление, выраженное как мутацию (функция производителя), и Immer вернет результат как новое состояние, не затрагивая текущее состояние.

Следует отметить, что, поскольку Immer сам позаботится о возврате окончательного черновика, аргумент функции producer не должен изменять черновик, а также пытаться что-то вернуть - он должен делать только одно или другое.

С помощью этой produce функции использовать Immer с Redux чрезвычайно просто. Учитывая тот же редуктор, что и раньше, использование Immer лучше, чем Seamless и Immutable.js, потому что:

  • Состояние - простой JavaScript.
  • Обновления этого состояния в редукторе выполняются с использованием простого JavaScript.
  • Все свойства, производные от этого состояния, представляют собой простой JavaScript.

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

Альтернатива 3: Redux Toolkit

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

Одна из таких утилит - createReducer(), которая незаметно использует Immer, позволяя записывать неизменяемые обновления состояния в виде мутаций.

Эта утилита также позволяет упростить структуру редукторов (до свидания, операторы switch), благодаря чему редуктор в целом выглядит немного по-другому, но фактическое обновление состояния выглядит так же, как с Immer:

Таким образом, использование Redux Toolkit дает те же преимущества, что и использование Immer, только функция produce скрыта от глаз.

Альтернатива 4: Никакой библиотеки

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

Самостоятельная работа, вероятно, также не будет масштабироваться, но для небольших приложений, которые останутся небольшими (нужны ли небольшим приложениям Redux?), Это решение не хуже любых других.

Проще говоря: просто не забудьте { ...spreadEverything } в редукторах:

Продолжая, вы можете использовать Object.freeze() для обеспечения неизменности ваших данных - попытка изменить замороженный объект вызовет ошибку.

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

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

Резюме

Есть много доступных библиотек, помогающих с неизменяемыми данными в Redux, но те, которые обратно совместимы с простым JavaScript, на мой взгляд, являются победителями, что очень быстро обесценивает Immutable.js.

Прямо сейчас комбинация Immer + Redux кажется фаворитом и не без оснований - Immer не предлагает ничего, что нельзя было бы обработать, используя простой JavaScript.

Это означает, что он ограничен только редюсерами, в отличие от Immutable.js, который распространяется далеко за пределы специфичных для Redux частей кодовой базы. Все это делает Immer простым для понимания и использования, простым в использовании и даже простым удалением при необходимости (но я сомневаюсь, что так и будет)!

И помните, что для небольших приложений вам может вообще не понадобиться библиотека. По крайней мере, попробуйте перед импортом.