Redux FAQ: Производительность
Содержание
- Насколько хорошо “масштабируется” Redux с точки зрения производительности и архитектуры?
- Не будет ли вызов “всех моих редюсеров” для каждого действия медленным?
- Должен ли я иметь полноценный клон моего состояния в редюсере? Не будет ли копирование моего состояния медленным?
- Как мне уменьшить количество событий обновления стора?
- Будут ли проблемы с памятью из-за использования “одного дерева состояния”? Будет ли вызов большого количества действий занимать память?
Производительность
Насколько хорошо “масштабируется” Redux с точки зрения производительности и архитектуры?
Пока еще нет окончательного ответа на этот вопрос, в большинстве случаев это не должно быть проблемой.
Работа Redux как правило, делится на несколько частей:
- обработка действие в мидлварах и редюсерах (включая дублирование объекта для постоянных обновлений),
- уведомление подписчиков после отправки действий,
- обновление UI-компонентов на основе изменения состояния.
Хотя, конечно, каждый из этих пунктов может ухудшить производительность в достаточно сложных ситуациях, но в реализации нет Redux изначально нет ничего медлительного или неэффективного. По факту, React Redux сильно оптимизирован в части избавлениях от ненужных перерисовок, а React-Redux v5 показывает заметные улучшение по сравнению с более поздними версиями.
Redux может быть не так эффективен из коробки, по сравнению с другими библиотеками. Для максимальной производительности отрисовки в React-приложении, состояние должно храниться в нормализованной форме, множество компонентов должны подключаться к одному стору, а не к нескольким, и подключенный список компонентов должен передавать идентификатор каждого элемента своим подключенным потомкам (позволяя тем самым элементам списка искать их собственные данные по ID). Это минимализирует общее количество перерисовок. Использование мемоизированных функций также является важным фактором эффективности.
С точки зрения архитектуры, неофициальные данные показывают, что Redux хорошо работает с различными проектами и размерами команды. В настоящее время Redux используется сотнями компаний и тысячами разработчиков, имеет несколько сотен тысяч ежемесячных установок из NPM. Один разработчик сообщил:
для сравнения, у нас есть ~500 типов экшенов, ~400 редюсеров, ~150 компонентов, 5 мидлваров, ~200 экшенов, ~2300 тестов
Дополнительная информация
Документация
Статьи
- How to Scale React Applications (accompanying talk: Scaling React Applications)
- High-Performance Redux
- Improving React and Redux Perf with Reselect
- Encapsulating the Redux State Tree
- React/Redux Links: Performance - Redux
Обсуждения
- #310: Who uses Redux?
- #1751: Performance issues with large collections
- React Redux #269: Connect could be used with a custom subscribe method
- React Redux #407: Rewrite connect to offer an advanced API
- React Redux #416: Rewrite connect for better performance and extensibility
- Redux vs MobX TodoMVC Benchmark: #1
- Reddit: What's the best place to keep the initial state?
- Reddit: Help designing Redux state for a single page app
- Reddit: Redux performance issues with a large state object?
- Reddit: React/Redux for Ultra Large Scale apps
- Twitter: Redux scaling
- Twitter: Redux vs MobX benchmark graph - Redux state shape matters
- Stack Overflow: How to optimize small updates to props of nested components?
- Chat log: React/Redux perf - updating a 10K-item Todo list
- Chat log: React/Redux perf - single connection vs many connections
Не будет ли вызов “всех моих редюсеров” для каждого действия медленным?
Важно иметь ввиду, что Redux-стор на самом деле имеет всего одну функцию-редюсер. Стор передает текующее состояние и отправленный экшен этому единственному редюсеру и позволяет ему обрабатывать это все как надо.
Очевидно, попытка обрабатывать каждое возможное действие в одной функции плохо масштабируется, хотя бы с точки зрения размера функции и читабельности. Таким образом, есть смысл разделить реальную работу на отдельные функции, которые могут быть вызваны редюсером верхнего порядка. В частности, общий рекомендуемый подход — это иметь отдельную функцию (подредюсер), которая ответственна за управление обновлениями на определенном участке состояния в определенном ключе. Функция combineReducers()
, поставляемая с Redux, — 1 из множества способов реализации этого подхода. Также настоятельно рекомендуется держать состояние стора единообразным и нормализированным на столько, на сколько это возможно. В конечном счете, только Вы отвечаете за организацию логики Вашего редюсера любыми способами, какими только пожелаете.
Однако, даже если у Вас много различных редюсеров, объединенных вместе, даже с глубоко вложенным состоянием, скорость редюсера вряд ли будет проблемой. Движки JavaScript способны запускать очень большое количество вызовов функций в секунду, а большинство Ваших редюсеров вероятнее всего используют конструкцию switch
и возвращают существующее состояние по умолчанию в ответ на большинство действий.
Если вы действительно обеспокоены производительностью редюсера, вы можете использовать такие утилиты, как redux-ignore или reduxr-scoped-reducer, чтоб гарантировать, что только определенные редюсеры прослушивают конкретные действия. Также Вы можете использовать redux-log-slow-reducers, чтобы провести сравнительный анализ эффективности.
Дополнительная информация
Обсуждения
- #912: Proposal: action filter utility
- #1303: Redux Performance with Large Store and frequent updates
- Stack Overflow: State in Redux app has the name of the reducer
- Stack Overflow: How does Redux deal with deeply nested models?
Должен ли я иметь полноценный клон моего состояния в редюсере? Не будет ли копирование моего состояния медленным?
Иммутабельное обновление состояния в большинстве случаев подразумевает создание поверхностных, неглубоких копий. Поверхностные копии более быстрые, чем полноценные, потому что предполагают копирование небольших объектов и полей, и это эффективно снижает перемещение нескольких указателей.
Однако, Вам надо создавать скопированные и обновленные объекты для каждого затронутого уровня вложенности. Хотя это не должно быть особенно затратно, но это еще одна причина, почему Вам следует держать Ваше состояние нормализованным и поверхностным на сколько возможно.
Распростораненное заблуждение Redux: Вам надо полноценно клонировать состояние. На самом деле: если внутри ничего не меняется, достаточно хранить ссылку!
Дополнительная информация
Документация
- Рецепты: Структурирование редюсеров - Предварительные концепциии
- Рецепты: Структурирование редюсеров - Паттерны иммутабельного обновления
Обсуждения
- #454: Handling big states in reducer
- #758: Why can't state be mutated?
- #994: How to cut the boilerplate when updating nested entities?
- Twitter: common misconception - deep cloning
- Cloning Objects in JavaScript
Как мне уменьшить количество событий обновления стора?
Redux уведомляет подписчиков после каждой успешной отправки действия (т.е. действие достигнуло стора и было обработано редюсерами). В некоторых случаях, может быть полезно урезать количество вызовов подписчиков, особенно если генератор экшенов отправляет несколько отдельных действий за раз.
Если Вы используете React, помните, что Вы можете улучшить производительность нескольких синхронных отправок обертыванием их в ReactDOM.unstable_batchedUpdates()
, но этот API экспериментален и может быть удален в любом выпуске React, поэтому не полагайтесь на это слишком сильно. Взгляните на аналоги:
- redux-batched-actions — редюсер высокого порядка, который позволяет Вам отправлять несколько действий так, будто это одно действие, и “распаковывать” их в редюсере,
- redux-batched-subscribe — расширитель стора, который позволяет Вам вызывать подписчиков для множественных отправок не чаще, чем раз в определенное время (debounce),
- redux-batch — расширитель стора, которых обрабатывает отправку набора экшенов с уведомлением одного подписчика.
Дополнительная информация
Обсуждения
- #125: Strategy for avoiding cascading renders
- #542: Idea: batching actions
- #911: Batching actions
- #1813: Use a loop to support dispatching arrays
- React Redux #263: Huge performance issue when dispatching hundreds of actions
Libraries
Будут ли проблемы с памятью из-за использования “одного дерева состояния”? Будет ли вызов большого количества действий занимать память?
Во первых, с точки зрения использования ресурсов памяти, Redux не сильно отличается от других JavaScript-библиотек. Единственно отличие — все возможные ссылки на объекты вместе вложены в одно дерево, а не хранятся в различных независимах экземплярах модели, как например в Backbone.
Во-вторых, типичное Redux-приложение, вероятно, отчасти меньше использует память, чем такое же Backbone-приложение, из-за того, что Redux поощряет использование простых JavaScript-объектов и массивов вместо создания экземпляров моделей и коллекций.
Наконец, Redux держит в памяти в один момент времени только одно дерево состояния со ссылками. Объекты, которые больше не имеют ссылок в этом дереве, обычно будут собраны сборщиком мусора.
Redux сам не хранит историю действий. Однако, Redux DevTools — хранит, таким образом действия могут быть проиграны заново, но это возможно только в процессе разработки и не используется в продакшн-версии.
Дополнительная информация
Документация
Обсуждения