Перейти к основному содержимому

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

Неофициальный Бета-перевод

Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →

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

Насколько хорошо Redux "масштабируется" в плане производительности и архитектуры?

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

Работа Redux обычно включает несколько направлений: обработку действий в middleware и редьюсерах (включая копирование объектов для неизменяемых обновлений), уведомление подписчиков после диспетчеризации действий и обновление UI-компонентов при изменениях состояния. Хотя каждое из этих направлений может стать узким местом в достаточно сложных сценариях, в самой реализации Redux нет ничего изначально медленного или неэффективного. Более того, React Redux специально оптимизирован для сокращения лишних ререндеров, а версия 5 демонстрирует заметные улучшения по сравнению с предыдущими релизами.

Из коробки Redux может быть менее эффективен по сравнению с другими библиотеками. Для максимальной производительности рендеринга в React-приложениях следует хранить состояние в нормализованном виде, подключать к хранилищу множество отдельных компонентов вместо нескольких, а компоненты списков должны передавать дочерним элементам ID элементов (что позволяет элементам списка самостоятельно получать свои данные по ID). Это минимизирует общий объём рендеринга. Также важным аспектом производительности является использование мемоизированных селекторов.

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

в нашем проекте: ~500 типов действий, ~400 кейсов в редьюсерах, ~150 компонентов, 5 middleware, ~200 действий, ~2300 тестов

Дополнительные материалы

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

Статьи

Обсуждения

Не замедлит ли вызов «всех моих редьюсеров» для каждого действия работу?

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

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

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

Если вас беспокоит производительность редьюсеров, вы можете использовать такие утилиты как redux-ignore или reduxr-scoped-reducer, чтобы гарантировать, что только определённые редьюсеры реагируют на конкретные действия. Для бенчмаркинга производительности также подойдёт redux-log-slow-reducers.

Дополнительные материалы

Обсуждения

Нужно ли делать глубокое клонирование состояния в редьюсере? Разве копирование состояния не замедлит работу?

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

Кроме того, глубокое клонирование состояния создаёт новые ссылки для каждого поля. Поскольку функция connect из React-Redux полагается на сравнение ссылок для определения изменений данных, это приведёт к принудительному ререндеру UI-компонентов без реальной необходимости, даже если значимые данные не изменились.

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

Распространённое заблуждение о Redux: нужно глубоко клонировать состояние. Реальность: если внутренние данные не меняются — сохраняйте их ссылочную целостность!

Дополнительные материалы

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

Обсуждения

Как уменьшить количество событий обновления хранилища?

Redux уведомляет подписчиков после каждого успешно отправленного действия (которое достигло хранилища и было обработано редьюсерами). В некоторых случаях полезно сократить количество вызовов подписчиков, особенно если action creator отправляет несколько отдельных действий подряд.

Существует несколько аддонов, реализующих пакетную обработку: redux-batched-actions (редьюсер высшего порядка, позволяющий отправлять несколько действий как одно и "распаковывать" их в редьюсере), redux-batched-subscribe (улучшитель хранилища, дебаунсирующий вызовы подписчиков при множественных отправках) или redux-batch (улучшитель хранилища, обрабатывающий массив действий с одним уведомлением подписчиков).

Для React-Redux, начиная с v7, появился API batch, минимизирующий количество ререндеров React при отправке действий вне обработчиков событий. Он использует React API unstable_batchedUpdate(), объединяя все обновления в рамках одного тика событий в один проход рендеринга. React уже применяет это внутренне для своих обработчиков событий. Данный API является частью рендереров (ReactDOM/React Native), а не ядра React.

Поскольку React-Redux работает в разных средах, мы импортируем этот API из соответствующего рендерера при сборке. Мы также реэкспортируем функцию публично под именем batch(). Её можно использовать для гарантии, что множественные действия вне React вызовут только одно обновление, как показано в примере:

import { batch } from 'react-redux'

function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}

Дополнительные материалы

Обсуждения

Библиотеки

Вызовет ли «единое дерево состояния» проблемы с памятью? Займут ли множество диспатчей действий много памяти?

Во-первых, с точки зрения чистого использования памяти, Redux ничем не отличается от любой другой JavaScript-библиотеки. Единственное отличие — все ссылки на объекты вложены в единое дерево, вместо хранения в независимых экземплярах моделей, как в Backbone. Во-вторых, типичное приложение на Redux, вероятно, будет использовать меньше памяти, чем аналогичное приложение на Backbone, поскольку Redux поощряет использование простых объектов и массивов JavaScript вместо создания экземпляров моделей и коллекций. Наконец, Redux хранит только одну актуальную ссылку на дерево состояния. Объекты, на которые больше нет ссылок в этом дереве, будут собраны сборщиком мусора, как обычно.

Сам Redux не хранит историю действий. Однако Redux DevTools сохраняет действия для возможности их воспроизведения, но обычно они активны только в режиме разработки и не используются в production.

Дополнительные материалы

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

Обсуждения

Вызовет ли кэширование удалённых данных проблемы с памятью?

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

Вот несколько подходов для эффективного кэширования удалённых данных:

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

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

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

Дополнительные материалы

Обсуждения