Перейти к содержанию

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,
  • библиотеки, которые оборачивают логику иммутабельного обновления в простые функции.

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

Статьи

Обсуждения

Почему мой компонент перерендеривается слишком часто?

React Redux реализует несколько оптимизаций, чтобы обеспечить перерисовоку компонента только тогда, когда это действительно необходимо. Одна из них — поверхностное сравнение объединенных в объект параметров функции mapStateToProps и аргументов mapDispatchToProps, переданных в connect. К несчастью, поверхностное сравнение не помогает в случаях, когда новый массив или ссылка на объект создаются при каждом вызове . Типичным примером может быть сопоставление массива идентификаторов с соответствующими ссылками на объекты, как, например:

1
2
3
4
5
6
7
const mapStateToProps = (state) => {
    return {
        objects: state.objectIds.map(
            (id) => state.objects[id]
        ),
    };
};

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

Излишние перерисовки могут быть решены сохранением массива объектов в состоянии с использованием редьюсера, с кэшированием сопоставляемого массива с использованием Reselect, или реализацией shouldComponentUpdate внутри компонента вручную и выполнением более глубокого сравнения параметров с использованием таких функций, как _.isEqual. Будьте осторожны, чтобы не сделать shouldComponentUpdate() более затратным, чем сам рендеринг! Всегда используйте порфилировщик для проверки Ваших предположений касаемо производительности.

Для неподключенных компонентов Вы можете проверять, какие параметры передаются. Общая проблема — это наличие в родительском компоненте привязки функции к контексту внутри рендера, как, например, <Child onClick={this.handleClick.bind(this)} />. Это создает новую функциональную зависимость каждый раз, когда родитель перерисовывается. Считается хорошей практикой привязывать функции к контексту один раз в конструкторе компонента-родителя.

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

Статьи

Обсуждения

Библиотеки

Как я могу ускорить мой mapStateToProps?

Пока React Redux делает работу по минимизации числа вызовов функции mapStateToProps, все еще надо быть уверенным, что Ваш mapStateToProps запускается быстро и также минимизирует количество выполняемой работы. Общий рекомендуемый подход — это создавать мемоизированные селекторы функций, используя Reselect. Эти селекторы могут быть объединены и составлены вместе, селекторы позже будут запущены только в том случае, если входные данные изменились. Это значит, что Вы можете создать селекторы, которые делают такие вещи, как фильтрация или сортировка, и быть уверенными, что на самом деле они будут работать только когда это нужно.

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

Статьи

Обсуждения

Почему у меня недоступен this.props.dispatch в моем подсоединенном компоненте?

Функция connect() получает 2 основных аргумента, оба необязательных. Первый — mapStateToProps — функция, которую Вы предоставляете для чтения данных из стора, при их изменении, и передачи этих значений в качестве агрументов в Ваш компонент. Вторая — mapDispatchToProps — функция, которую Вы предоставляете для возможности использовать dispatch метод стора, обычно создавая предварительно связанные (pre-bound) версии генераторов экшенов, которые будут автоматически отправлять свои экшены как только будут вызваны.

Если Вы не передаете Вашу собственную функцию mapDispatchToProps при вызове connect(), React Redux передаст версию по умолчанию, которая просто возвращает dispatch функцию как параметр. Это означает, что если Вы передаете свою функцию, то dispatch автоматически не передается. Если Вы все еще хотите получить dispatch как аргумент, Вам надо явно возвращать его самим в Вашей реализации mapDispatchToProps.

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

Обсуждения

Должен ли я подключать (connect) только мой корневой компонент или я могу подключить несколько компонентов в моем дереве?

Раньше документация Redux советовала Вам иметь несколько подключаемых компонентов рядом с корневым. Однако, время и опыт показали, что это, как правильно, требует от нескольких компонентов знать слишком много о требованиях к данным всех их потомков, и заставляет их передавать запутывающее количество параметров.

Текущаяя предложенная лучшая практика — это разделять Ваши компоненты на “представления” и “контейнеры” и извлекать подключенный контейнер компонента везде, где это имеет смысл:

Акцент на том, что “один компонент-контейнер в качестве корневого”, в примерах Redux был ошибкой. Не используйте это как афоризм. Пытайтесь хранить представления ваших компонентов отдельно. Создавайте контейнеры, подключая их когда это удобно. Всякий раз, когда Вы чувствуете, что Вы дублируете код родительского компонента, чтобы передать данные нескольким потомкам, пора извлечь контейнер. Как правило, как только вы чувствуете, что родитель знает слишком много о “личных” данных или действиях своих детей, пора извлечь контейнер.

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

В целом, старайтесь найти баланс между пониманием потока данных и областями ответственности Ваших компонентов.

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

Статьи

Обсуждения

Комментарии