FAQ по React Redux¶
Почему мой компонент не перерендеривается? Почему не работает mapStateToProps?¶
Случайные мутации или изменения состояния напрямую являются наиболее частыми причинами того, что компонент не перерисоваывается после отправки экшена. Redux ожидает, что Ваши редьюсеры будут обновлять свои состояни “иммутабельно”, что фактически означает, что надо делать копию данных и изменять уже копию. Если Вы возвращаете тот же самый объект в редьюсере, Redux полагает, что ничего не изменилось, даже если Вы изменили содержимое объекта. Аналогично React Redux старается улучшить производительность, делая поверхностное сравнение ссылок входных параметров в shouldComponentUpdate
, и, если все ссылки остались такими же, возвращает false
, пропуская обновление Вашего компонента.
Важно помнить, что всякий раз, когда вы обновляете вложенные значения, Вы должны также вернуть новые копии этих значений в Ваше дерево состояний. Если у Вас state.a.b.c.d
, и Вы хотите обновить данные в d
, Вам также надо будет вернуть новые копии c
, b
, a
, и state
. state tree mutation diagram показывает, как глубокое изменение в дереве меняет весь путь.
Важно понимать, что “иммутабельное обновление данных” не означает, что Вы должны использовать Immutable.js, хотя это конечно вариант. Вы можете делать иммутабельные обновления простых JS-объектов и массивов, используя несколько различных подходов:
- копирование объектов с использование таких функций, как
Object.assign()
и_.extend()
, а для массивов —slice()
иconcat()
, - использование spread-оператора (...) из ES6, который предполагается использовать в следующей версии JavaScript,
- библиотеки, которые оборачивают логику иммутабельного обновления в простые функции.
Документация
Статьи
Обсуждения
- #1262: Immutable data + bad performance
- React Redux #235: Predicate function for updating component
- React Redux #291: Should mapStateToProps be called every time an action is dispatched?
- Stack Overflow: Cleaner/shorter way to update nested state in Redux?
- Gist: state mutations
Почему мой компонент перерендеривается слишком часто?¶
React Redux реализует несколько оптимизаций, чтобы обеспечить перерисовоку компонента только тогда, когда это действительно необходимо. Одна из них — поверхностное сравнение объединенных в объект параметров функции mapStateToProps
и аргументов mapDispatchToProps
, переданных в connect
. К несчастью, поверхностное сравнение не помогает в случаях, когда новый массив или ссылка на объект создаются при каждом вызове . Типичным примером может быть сопоставление массива идентификаторов с соответствующими ссылками на объекты, как, например:
1 2 3 4 5 6 7 |
|
Хотя массив может каждый раз хранить в точности те же ссылки, сам массив имеет другую ссылку, а в этом случае поверхностное сравнение возвращает false
, и React Redux запускает перерисовку компонента-обертки.
Излишние перерисовки могут быть решены сохранением массива объектов в состоянии с использованием редьюсера, с кэшированием сопоставляемого массива с использованием Reselect, или реализацией shouldComponentUpdate
внутри компонента вручную и выполнением более глубокого сравнения параметров с использованием таких функций, как _.isEqual
. Будьте осторожны, чтобы не сделать shouldComponentUpdate()
более затратным, чем сам рендеринг! Всегда используйте порфилировщик для проверки Ваших предположений касаемо производительности.
Для неподключенных компонентов Вы можете проверять, какие параметры передаются. Общая проблема — это наличие в родительском компоненте привязки функции к контексту внутри рендера, как, например, <Child onClick={this.handleClick.bind(this)} />
. Это создает новую функциональную зависимость каждый раз, когда родитель перерисовывается. Считается хорошей практикой привязывать функции к контексту один раз в конструкторе компонента-родителя.
Документация
Статьи
- A Deep Dive into React Perf Debugging
- React.js pure render performance anti-pattern
- Improving React and Redux Performance with Reselect
- Encapsulating the Redux State Tree
- React/Redux Links: React/Redux Performance
Обсуждения
Библиотеки
Как я могу ускорить мой mapStateToProps
?¶
Пока React Redux делает работу по минимизации числа вызовов функции mapStateToProps
, все еще надо быть уверенным, что Ваш mapStateToProps
запускается быстро и также минимизирует количество выполняемой работы. Общий рекомендуемый подход — это создавать мемоизированные селекторы функций, используя Reselect. Эти селекторы могут быть объединены и составлены вместе, селекторы позже будут запущены только в том случае, если входные данные изменились. Это значит, что Вы можете создать селекторы, которые делают такие вещи, как фильтрация или сортировка, и быть уверенными, что на самом деле они будут работать только когда это нужно.
Документация
Статьи
Обсуждения
Почему у меня недоступен this.props.dispatch
в моем подсоединенном компоненте?¶
Функция connect()
получает 2 основных аргумента, оба необязательных. Первый — mapStateToProps
— функция, которую Вы предоставляете для чтения данных из стора, при их изменении, и передачи этих значений в качестве агрументов в Ваш компонент. Вторая — mapDispatchToProps
— функция, которую Вы предоставляете для возможности использовать dispatch
метод стора, обычно создавая предварительно связанные (pre-bound) версии генераторов экшенов, которые будут автоматически отправлять свои экшены как только будут вызваны.
Если Вы не передаете Вашу собственную функцию mapDispatchToProps
при вызове connect()
, React Redux передаст версию по умолчанию, которая просто возвращает dispatch
функцию как параметр. Это означает, что если Вы передаете свою функцию, то dispatch
автоматически не передается. Если Вы все еще хотите получить dispatch
как аргумент, Вам надо явно возвращать его самим в Вашей реализации mapDispatchToProps
.
Документация
Обсуждения
- React Redux #89: can i wrap multi actionCreators into one props with name?
- React Redux #145: consider always passing down dispatch regardless of what mapDispatchToProps does
- React Redux #255: this.props.dispatch is undefined if using mapDispatchToProps
- Stack Overflow: How to get simple dispatch from this.props using connect w/ Redux?
Должен ли я подключать (connect) только мой корневой компонент или я могу подключить несколько компонентов в моем дереве?¶
Раньше документация Redux советовала Вам иметь несколько подключаемых компонентов рядом с корневым. Однако, время и опыт показали, что это, как правильно, требует от нескольких компонентов знать слишком много о требованиях к данным всех их потомков, и заставляет их передавать запутывающее количество параметров.
Текущаяя предложенная лучшая практика — это разделять Ваши компоненты на “представления” и “контейнеры” и извлекать подключенный контейнер компонента везде, где это имеет смысл:
Акцент на том, что “один компонент-контейнер в качестве корневого”, в примерах Redux был ошибкой. Не используйте это как афоризм. Пытайтесь хранить представления ваших компонентов отдельно. Создавайте контейнеры, подключая их когда это удобно. Всякий раз, когда Вы чувствуете, что Вы дублируете код родительского компонента, чтобы передать данные нескольким потомкам, пора извлечь контейнер. Как правило, как только вы чувствуете, что родитель знает слишком много о “личных” данных или действиях своих детей, пора извлечь контейнер.
По факту, тесты показывают, что большее число подключенных компонентов, как правило, приводит к улучшению производительности, чем меньшее их количество.
В целом, старайтесь найти баланс между пониманием потока данных и областями ответственности Ваших компонентов.
Документация
Статьи
- Presentational and Container Components
- High-Performance Redux
- React/Redux Links: Architecture - Redux Architecture
- React/Redux Links: Performance - Redux Performance
Обсуждения
- Twitter: emphasizing “one container” was a mistake
- #419: Recommended usage of connect
- #756: container vs component?
- #1176: Redux+React with only stateless components
- Stack Overflow: can a dumb component use a Redux container?