Основы Redux

1. Введение

Требования к функционалу приложений постоянно растут, в результате растет количество состояний интерфейса: асинхронная загрузка данных, лоадеры, фильтры и т. п.

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

Идеальный вариант, это когда интерфейс вообще не знает о бизнес-логике. В этом нам помогают библиотеки управления состоянием. Redux и Mobx самые популярные, а для бекендов основаных на GraphQL есть Apollo.

2. Основные принципы Redux

  • Предсказуемость результата - существует всегда один источник правды, хранилище (store), хранящее в себе состояние приложения и методы для работы с ним.

  • Поддреживаемость - есть набор правил и лучших практик о том, как должен быть структурирован код, что делает его более единообразным и понятным.

  • Инструменты разработчика - все происходящее можно отслеживать в режиме реального времени.

  • Простота тестирования - первое правило написания тестируемого кода - писать небольшие функции, которые выполняют только одну вещь и независимы. Redux - это в основном функции: маленькие, чистые и изолированные.

2.1. Поток данных

Без использования библиотеки управления состоянием процесс обновления данных выглядит следующим образом: компонент инициализирует изменение состояния вызвав метод полученный пропом, после чего измененное состояние пробрасывается пропами вниз по дереву.

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

  • Пользователь, работая с интерфейсом, инициализирует отправку действий (actions)

  • Хранилище (store) вызывает все объявленные редьюсеры (reducers), передавая им текущее состояние (state) и действие (action)

  • Хранилище (store) сохраняет обновленное дерево состояния (state) возвращенное из редьюсеров (reducers)

  • При обновлении состояния (state) вызываются все подписчики для обновления интерфейса

3. Store

Хранилище (store) - js-объект, который содержит состояние приложения и методы доступа к нему, отправки действий и регистрации слушателей.

  • Хранит состояние (state) приложения как один объект

  • Позволяет получить доступ к состоянию через метод getState()

  • Напрямую состояние доступно только для чтения

  • Единственный способ изменить состояние - отправить действие (action), объект, описывающий, что произошло

  • Для отправки дейсвтий есть метод dispatch(action)

  • Изменения производятся с использованием чистых функций - редюсеров (reducers), которые реагируют на действия

  • Регистрирация слушателей делается методом subscribe(listener)

Так как все состояние приложения хранится как один объект, стоит подумать о форме состояния прежде чем писать какой-либо код. Продумайте минимальное представление состояния приложения в виде объекта.

3.1. Функция createStore

Для того чтобы создать хранилище, используется функция createStore, которая принимает набор параметров и возвращает созданное хранилище.

  • reducer - функция, которая возвращает следующее дерево состояния, учитывая текущее дерево состояния и действие для обработки.

  • preloadedState - начальное состояние, к примеру сериализаванное состояние последнего пользовательского сеанса. Это должен быть объект той же формы, что и, как минимум, часть состояния.

  • enhancer - расширяет возможности хранилища при помощи прослоек (middleware).

4. Actions

Действия (actions) - это объекты, которые помогают доставить данные из компонентов в хранилище.

  • Хранят минимально необходимый набор информации.

  • Должны иметь свойство type, которое указывает тип выполняемого действия.

  • Помимо поля type, структура действия может быть произвольной.

Действия создаются функциями (action creators), которые могут быть асинхронными и иметь побочные эффекты. В базовом варианте они просто возвращают объект-дейтсвие.

5. Reducers

Редюсеры (reducers) - это чистые функции, которые принимают предыдущее состояние приложения и действие, а затем возвращают новое следующее состояние.

Они определяют, как изменяется состояние приложения в ответ на действия, отправленные в хранилище. Помните, что действия описывают только то, что произошло, а не как изменяется состояние приложения.

Вещи, которые нельзя делать внутри редюсера:

  • Мутировать аргументы

  • Выполнять побочные эффекты, такие как API-запросы и т. п.

  • Вызывать нечистые функции, например Date.now()

Как выполнять побочные эффекты мы рассмотрим далее, пока что просто помните - редюсер должен быть чистым. Получая те же аргументы, он должен вычислить следующее состояние и вернуть его. Без сюрпризов. Никаких побочных эффектов. Никаких мутаций. Просто рассчет.

Вот редьюсер, который принимает текущее состояние и действие как аргументы, а затем возвращает следующее состояние.

Обратите внимание:

  • Мы создаем копию state, а не мутируем его.

  • Мы возвращаем предыдущее состояние по умолчанию. Важно вернуть предыдущее состояние для любого неизвестного действия.

Why Redux need reducers to be "pure functions"

6. React и Redux

Для того чтобы использовать React и Redux вместе, необходимо добавить пакет react-redux. Это набор компонентов связывающих React-компоненты и Redux-хранилище через контекст Context.

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

6.1. Provider

Компонент <Provider>, оборачивает все дерево компонентов приложения и, используя контекст, предоставляет store и его методы.

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

6.2. connect()

Если какой-либо компонент хочет получить доступ к store, он должен быть обернут в функцию connect(), которая свяжет компонент и store. Предоставляет доступ к state и disaptch().

connect это HOC, он не изменяет переданный ему компонент, а возвращает новый компонент связанный с хранилищем.

mapStateToProps(state, [ownProps]) - функция соединяющая часть состояния с пропами компонента. Таким образом, связанный компонент будет иметь доступ к необходимой ему части состояния.

  • Получает state как параметр и позволяет выбрать из всего state только необходимые компоненту слайсы (части).

  • Возвращает объект, свойства которого дополнят props компонента.

  • Вызывается каждый раз когда хранилище обновляется.

  • Если нет необходимости подписываться на обновления, передаем null.

  • Если функция объявлена ​​как принимающая два параметра, первым будет передана ссылка на state, а вторым ссылка на пропы самого компонента.

mapDispatchToProps(dispatch, [ownProps])- функция соединяющая отправку действий с пропами компонента. Таким образом, связанный компонент сможет отправлять действия посредством вызова методов указанных в возвращаемом объекте.

  • Получает ссылку на метод dispatch как параметр и позволяет объявить методы для отправки действий.

  • Возвращает объект, свойства которого дополнят props компонента.

  • Если функция объявлена ​​как принимающая два параметра, первым будет передана ссылка на dispatch, а вторым ссылка на пропы самого компонента.

Если аргументы действия совпадают с параметрами объявляемого метода, можно вместо функции передать объект. В таком случае connect пройдет по ключам объекта и обернет их в dispatch.

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

7. Redux DevTools

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

Документация redux-devtools-extension

8. Организация хранилища

Необходимо хранить все состояние приложения в Redux? Можно ли использовать setState()? Как разработчик, ваша задача - определить, из какого набора данных состоит приложение, и где их лучше хранить.

Некоторые общие правила для определения того, какие данные должны быть помещены в Redux. Если ответ да, то есть смысл использовать хранилище, а не локальное состояние.

  • Необходимы ли эти данные другим частям приложения?

  • Необходимо ли на основе этих данных создавать дополнительные производные?

  • Используются ли одни и те же данные для управления несколькими компонентами?

  • Есть ли необходимость в кешировании?

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

9. Композиция редюсеров

Еще одна полезная функция Redux - возможность делать композицию редьюсеров, то есть совмещать много в один. Это позволяет удобно поддерживать гораздо более сложные состояния в больших приложениях. Для этого используется функция combineReducers().

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

Last updated

Was this helpful?