React Redux
Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →
Redux FAQ: React Redux
Зачем мне использовать React-Redux?
Redux — это самостоятельная библиотека, которую можно использовать с любым UI-слоем или фреймворком, включая React, Angular, Vue, Ember и vanilla JS. Хотя Redux и React часто используются вместе, они независимы друг от друга.
При использовании Redux с любым UI-фреймворком обычно применяется библиотека «UI binding» для интеграции Redux с фреймворком, вместо прямого взаимодействия со store из UI-кода.
React-Redux — это официальная библиотека связки Redux для React. Если вы используете Redux и React вместе, вам также следует использовать React-Redux для соединения этих библиотек.
Хотя можно вручную писать логику подписки на store Redux, это приведёт к повторяющемуся коду. Кроме того, оптимизация производительности UI потребует сложной логики.
Процесс подписки на store, проверки обновлённых данных и запуска перерисовки можно сделать универсальным и переиспользуемым. Библиотека связки UI вроде React-Redux берёт на себя логику взаимодействия со store, избавляя вас от необходимости писать этот код самостоятельно.
В целом, React-Redux способствует хорошей архитектуре React и реализует за вас сложную оптимизацию производительности. Она также поддерживает актуальность с учётом последних изменений API в Redux и React.
Дополнительные материалы
Документация
Почему мой компонент не перерисовывается или не срабатывает mapStateToProps?
Непреднамеренное прямое изменение (мутация) состояния — самая распространённая причина, по которой компоненты не перерисовываются после диспатча экшена. Redux ожидает, что редюсеры будут обновлять состояние «иммутабельно» — создавая копии данных и применяя изменения к ним. Если возвращается тот же объект, Redux считает, что ничего не изменилось, даже если вы модифицировали его содержимое. Аналогично, React Redux оптимизирует производительность через поверхностные проверки ссылок (shallow equality) в shouldComponentUpdate: если все ссылки идентичны, shouldComponentUpdate возвращает false, и компонент пропускает обновление.
Важно помнить: при обновлении вложенного значения нужно возвращать новые копии всех родительских объектов в дереве состояния. Для обновления d в state.a.b.c.d потребуются новые копии c, b, a и самого state. Эта диаграмма мутации дерева состояния показывает, как изменение в глубине дерева требует обновлений на всех уровнях.
«Иммутабельное обновление данных» не обязывает использовать Immer (хотя это возможно). Обновлять обычные JS-объекты и массивы можно несколькими способами:
-
Копирование объектов через
Object.assign()или_.extend()и методов массивов (slice(),concat()) -
Оператор расширения массивов (ES2015) и объектов (ES2018)
-
Вспомогательные библиотеки с упрощённой логикой иммутабельных обновлений
Дополнительные материалы
Документация
-
Использование Redux: Структурирование редюсеров — Основные понятия
-
Использование Redux: Структурирование редюсеров — Паттерны иммутабельных обновлений
Статьи
Обсуждения
-
React Redux #235: Функция-предикат для обновления компонента
-
React Redux #291: Должна ли mapStateToProps вызываться при каждом диспатче действия?
-
Stack Overflow: Есть ли более чистый/короткий способ обновления вложенного состояния в Redux?
Почему компонент перерисовывается слишком часто?
React Redux реализует несколько оптимизаций, чтобы гарантировать перерисовку компонента только при необходимости. Одна из них — поверхностное сравнение (shallow equality) объекта пропсов, созданного mapStateToProps и mapDispatchToProps, переданных в connect. К сожалению, поверхностное сравнение не работает при создании новых экземпляров массивов или объектов при каждом вызове mapStateToProps. Типичный пример — преобразование массива ID в соответствующие объекты:
const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}
Даже если массив содержит те же ссылки на объекты, сам массив — новая ссылка, поэтому поверхностное сравнение проваливается и React Redux перерисует компонент.
Избыточных перерисовок можно избежать: сохраняя массив объектов в состоянии через редюсер, кэшируя преобразованный массив с помощью Reselect или реализуя shouldComponentUpdate с глубоким сравнением пропсов через функции типа _.isEqual. Убедитесь, что кастомный shouldComponentUpdate() не дороже самой перерисовки! Всегда проверяйте предположения о производительности в профайлере.
Для компонентов без подключения (non-connected) проверьте передаваемые пропсы. Распространённая проблема — перепривязка колбэка в render родителя, например <Child onClick={this.handleClick.bind(this)} />, создающая новую ссылку на функцию при каждой перерисовке. Рекомендуется привязывать колбэки один раз в конструкторе родителя.
Дополнительные материалы
Документация
Статьи
-
Антипаттерн производительности для чистого рендеринга в React.js
-
Улучшение производительности React и Redux с помощью Reselect
Обсуждения
Библиотеки
Как ускорить выполнение mapStateToProps?
Хотя React Redux действительно минимизирует количество вызовов функции mapStateToProps, всё равно важно обеспечить быстрое выполнение mapStateToProps и минимальный объём работы, которую она выполняет. Рекомендуемый подход — создание мемоизированных функций-селекторов с помощью Reselect. Эти селекторы можно комбинировать и объединять в цепочки, причём последующие селекторы выполняются только при изменении входных данных. Это позволяет создавать селекторы для фильтрации или сортировки, гарантируя выполнение ресурсоёмких операций только при необходимости.
Дополнительные материалы
Документация
Статьи
Обсуждения
Почему в подключённом компоненте недоступен this.props.dispatch?
Функция connect() принимает два необязательных аргумента. Первый — mapStateToProps — используется для получения данных из хранилища и передачи их в компонент через пропсы. Второй — mapDispatchToProps — позволяет использовать dispatch, обычно через предварительно связанные версии создателей действий (action creators), которые автоматически диспатчат действия при вызове.
Если вы не передаёте собственную функцию mapDispatchToProps в connect(), React Redux предоставит реализацию по умолчанию, которая добавляет dispatch как пропс. При использовании кастомной функции dispatch не передаётся автоматически. Для его получения необходимо явно вернуть его в вашей реализации mapDispatchToProps.
Дополнительные материалы
Документация
Обсуждения
-
React Redux #89: Можно ли объединить несколько создателей действий в один пропс?
-
React Redux #145: Предложение всегда передавать dispatch независимо от mapDispatchToProps
-
React Redux #255: this.props.dispatch равен undefined при использовании mapDispatchToProps
-
Stack Overflow: Как получить dispatch из this.props при использовании connect с Redux?
Стоит ли подключать только верхний компонент или можно подключить несколько компонентов в дереве?
Ранние рекомендации Redux советовали подключать лишь несколько компонентов в верхней части дерева. Однако практика показала, что это заставляет компоненты знать слишком много о потребностях потомков и передавать избыточное количество пропсов.
Современный подход предполагает разделение на презентационные (presentational) и контейнерные (container) компоненты, а также создание подключённых контейнеров там, где это уместно:
Акцент на "одном контейнерном компоненте наверху" в примерах Redux был ошибкой. Не возводите это в абсолют. Держите презентационные компоненты изолированными. Создавайте контейнеры через подключение при необходимости. Если вы дублируете код в родительских компонентах для передачи данных однотипным потомкам — выносите контейнер. Если родитель знает слишком много о "личных" данных или действиях потомков — выносите контейнер.
Бенчмарки показывают, что большее количество подключённых компонентов обычно даёт лучшую производительность, чем меньшее.
Стремитесь к балансу между понятным потоком данных и зонами ответственности компонентов.
Дополнительные материалы
Документация
Статьи
Обсуждения
Как Redux сравнивается с React Context API?
Сходства
И Redux, и Context API React решают проблему "проброса пропсов". Оба позволяют передавать данные без необходимости прокидывать пропсы через несколько уровней компонентов. Внутри Redux использует React Context API для передачи хранилища по дереву компонентов.
Различия
Redux предоставляет возможности Redux DevTools Extension. Это инструмент автоматически логирует все действия приложения и позволяет "путешествовать во времени" - возвращаться к любому предыдущему состоянию. Redux также поддерживает middleware, где вы можете привязывать кастомные функции к каждому диспатчу действия, например для автоматического логирования событий или перехвата определённых действий.
React Context API работает с парой компонентов, взаимодействующих исключительно друг с другом. Это обеспечивает изоляцию нерелевантных данных. Также вы гибко управляете использованием данных в компонентах - можете предоставлять состояние родительского компонента и передавать контекстные данные как пропсы в обёрнутые компоненты.
Ключевое отличие в обработке данных: Redux хранит состояние всего приложения в едином объекте. Он вычисляет изменения через редюсеры и возвращает новое состояние для каждого действия. React Redux оптимизирует перерисовку компонентов, гарантируя обновление только при изменении нужных данных. Context же не хранит состояние - он лишь канал для передачи данных. Для изменения данных вы должны полагаться на состояние родительского компонента.