Что такое reducer react
Redux + React: основы
Redux является предсказуемым контейнером состояния для JavaScript приложений. Это позволяет вам создавать приложения, которые ведут себя одинаково в различных окружениях (клиент, сервер и нативные приложения), а также просто тестируются.
Redux решает проблему управления состоянием в приложении, предлагая хранить данные в глобальном State, и централизованно изменяя его.
Установка
Reducer
Это функция, которая принимает на вход команды и изменяет state. Если тип action неизвестен, возвращаем state. Пример реализации на JavaScript:
Redux-store
Store содержит всё дерево состояний приложения. Единственный способ изменить состояние внутри него — отправить на него action.
createStore(reducer)
Store — это не класс. Это просто объект с несколькими методами. Чтобы создать его, передайте свою функцию в createStore
getState()
Возвращает текущее дерево состояний вашего приложения. Он равен последнему значению, которое возвращает store’s reducer.
dispatch(action)
store.dispatch(action) — отправляет команду, и это единственный способ вызвать изменение состояния store.
Store’s reducer будет вызываться с текущим getState() результатом и заданным action, синхронно. Его возвращаемое значение будет считаться следующим состоянием. Он будет возвращен с новым getState(), и слушатели изменений будут немедленно уведомлены.
subscribe(listener)
Добавляет слушателя изменений. Вызывается каждый раз, когда store может быть изменён.
Как это работает вместе
Actions Creators
В store может передаваться много данных, поэтому бывает удобно сделать функции создатели действий.
bindActionCreators()
Превращает объект, значения которого являются actions creators, в объект с теми же ключами, но с каждым action creator, заключенным в dispatch-вызов, чтобы их можно было вызывать напрямую.
Единственный вариант использования для bindActionCreators- это когда вы хотите передать actions creators в компонент, который не знает о Redux, и вы не хотите передавать dispatch или хранить Redux в нем.
Структура проекта
Если у много actions creators, разумно вынести их в отдельный файл, или папку. То же касается Reducer’а.
React-Redux
Provider
connect()
connect — это компонент высшего порядка (HOC), который создаёт новые компоненты.
Redux. Простой как грабли
Мне уже доводилось заглядывать в репозиторий библиотеки redux, но откуда-то появилась мысль углубиться в его реализацию. Своим в некотором роде шокирующим или даже разочаровывающим открытием я хотел бы поделиться с сообществом.
TL;DR: базовая логика redux помещается в 7 строк JS кода.
О redux вкратце (вольный перевод заголовка на гитхабе):
Redux — библиотека управления состоянием для приложений, написанных на JavaScript.
Она помогает писать приложения, которые ведут себя стабильно/предсказуемо, работают на разных окружениях (клиент/сервер/нативный код) и легко тестируемы.
Я склонировал репозиторий redux, открыл в редакторе папку с исходниками (игнорируя docs, examples и прочее) и взялся за ножницы клавишу Delete:
… потому что мог. Ну или потому что поленился писать для них примеры. Но без корнер-кейсов они ещё менее интересны, чем то, что ждёт вас впереди.
А теперь давайте разберём то, что осталось
Пишем redux за 7 строк
Весь базовый функционал redux умещается в малюсенький файлик, ради которого вряд ли кто-нибудь будет создавать github репозиторий 🙂
Так устроен redux. 18 страниц вакансий на HeadHunter с поисковым запросом «redux» — люди, которые надеются, что вы разберетесь в 7 строках кода. Всё остальное — синтаксический сахар.
С этими 7 строками уже можно писать TodoApp. Или что угодно. Но мы быстренько перепишем TodoApp из документации к redux.
Уже на этом этапе я думал бросить микрофон со сцены и уйти, но show must go on.
Давайте посмотрим, как устроен метод.
combineReducers
Это метод, который позволяет вместо того, чтобы создавать один огромный reducer для всего состояния приложения сразу, разбивать его на отдельные модули.
Используется он так:
Дальше использовать этот store можно так же, как предыдущий.
Разница моего примера и описанного в той же документации к TodoApp довольно забавная.
В документации используют модный синтаксис из ES6 (7/8/∞):
и соответственно переименовывают todoReducer в todos и counterReducer в counter. И многие в своём коде делают то же самое. В итоге разницы нет, но для человека, знакомящегося с redux, с первого раза эта штука выглядит магией, потому что ключ части состояния (state.todos) соответствует функции, названной также только по желанию разработчика (function todos()<>).
Если бы нам нужно было написать такой функционал на нашем micro-redux, мы бы сделали так:
Этот код плохо масштабируется. Если у нас 2 «под-состояния», нам нужно дважды написать (state, action), а хорошие программисты так не делают, правда?
В следующем примере от вас ожидается, что вы не испугаетесь метода Object.entries и Деструктуризации параметров функции
Однако реализация метода combineReducers довольно простая (напоминаю, это если убрать валидацию и вывод ошибок) и самую малость отрефакторить на свой вкус:
Мы добавили к нашему детёнышу redux ещё 9 строк и массу удобства.
Перейдём к ещё одной важной фиче, которая кажется слишком сложной, чтобы пройти мимо неё.
applyMiddleware
middleware в разрезе redux — это какая-то штука, которая слушает все dispatch и при определенных условиях делает что-то. Логирует, проигрывает звуки, делает запросы к серверу,… — что-то.
В оригинальном коде middleware передаются как дополнительные параметры в createStore, но если не жалеть лишнюю строчку кода, то использование этого функционала выглядит так:
При этом реализация метода applyMiddleware, когда ты потратишь 10 минут на ковыряние в чужом коде, сводится к очень простой вещи: createStore возвращает объект с полем «dispatch». dispatch, как мы помним (не помним) из первого листинга кода, — это функция, которая всего лишь применяет редюсер к нашему текущему состоянию (newState = reducer(state, action)).
Так вот applyMiddleware не более чем переопределяет метод dispatch, добавляя перед (или после) обновлением состояния какую-то пользовательскую логику.
Возьмём, например, самый популярный middleware от создателей redux — redux-thunk
Его смысл сводится к тому, что можно делать не только
но и передавать в store.dispatch сложные функции
И теперь, когда мы выполним команду
я понимаю, что конструкция выглядит жутковато, но её тоже просто нужно вызвать пару раз с произвольными параметрами и вы осознаете, что всё не так страшно, это просто функция, возвращающая функцию, возвращающую функцию (ладно, согласен, страшно)
Напомню, оригинальный метод createStore выглядел так
То есть он принимал атрибуты (reducer, initialState) и возвращал объект с ключами < dispatch, getState >.
Оказалось, что реализовать метод applyMiddleware проще, чем понять, как он работает.
Мы берём уже реализованный метод createStore и переопределяем его возвращаемое значение:
Вывод
Под капотом redux содержатся очень простые логические операции. Операции на уровне «Если бензин в цилиндре загорается, давление увеличивается». А вот то, сможете ли вы построить на этих понятиях болид Формулы 1 — уже решайте сами.
Что такое reducer react
Redux представляет собой контейнер предсказуемого состояния данных для JavaScript, что позволяет создавать надежные и легко расширяемые приложения, которые будут работать в различных окружениях исполнения.
В данной статье мы рассмотрим простой алгоритм подключения Redux к React-приложению, и последующей работы с ним.
Мы разберем каким образом подключить Redux к приложению, а также – где, когда и в какой последовательности использовать Actions, Reducers и Dispatchers.
Для простоты понимания, сначала мы произведем все необходимые действия в одном JavaScript файле, затем разобьём получившийся код на несколько файлов, т.е. создадим начальную структуру приложения. И в конце всего подключим полезный пакет react-redux, который позволит значительно упростить работу с Redux.
Часть 1
1. Подготовка
В большинстве туториалах по использованию React & Redux предварительно создается довольно-таки большая структура приложения, с применением сборки приложения и различных дополнительных инструментов. Все это может создать (и создаст, поверьте) трудности для понимания работы Redux, принцип которой весьма прост. Чтобы избежать ненужных трудностей, мы будем использовать минимально необходимый набор инструментов для работы нашего приложения.
Стартовый шаблон приложения можно скачать по ссылки из репозитория проекта.
Разберем более подробно необходимый минимум для работы нашего приложения.
В файле package.json содержится несколько пакетов, которые обеспечат корректное функционирование приложения:
Ну и самое главное, в файле src/index.html мы просто подключим библиотеки React & Redux по ссылке – всё как в старые добрые времена 🙂
Добавим в файл src/app.js начальный код для проверки работоспособности нашего приложения:
2. Создаем Store
Первым делом создадим хранилище, которое будет общим для всего приложения:
3. Создаем Reducer
Затем создадим редуктор posts :
Каждый редуктор принимает следующие параметры:
И возвращает или новое состояние данных, или состояние данных приложения по умолчанию.
4. Создаем Action Creators
Создадим Action Creators с именем addPost и действием ADD_POST :
Action Creators вернет объект с типом действия и любыми дополнительными данными, которые нам нужны.
5. Создаем дочерний компонент
Дочерний компонент нам понадобится для наглядной демонстрации Dispatchers и работы с действиями. Для начала изменим основной компонент, чтобы он смог вывести дочерний:
Затем создадим дочерний компонент, который, к примеру, будет отвечать за вывод постов:
6. Запускаем приложение
В данном случае, при каждом изменении store, будет запускаться run() функция и в компонент Content передадутся актуальные данные.
Часть 2
7. Разбиваем приложение на файлы
Для начала уберем из index.html подключение React и Redux:
Создадим файл src/actions.js и поместим в него код Action Creators из файла app.js. Сейчас это только одна функция addPost() :
Теперь переместим редуктор в файл src/reducers.js :
И дочерний компонент переместим в файл src/components/Content.js :
И конечно же не забудем подключить созданные файлы и нужные модули в файле src/app.js :
Часть 3
8. Подключаем пакет react-redux
По своей сути, react-redux предоставляет удобную реализацию доступа компонентам к store и работы с actions.
Для начала установим этот пакет:
В качестве параметра, компонент Provider принимает наше store.
Теперь дочерний компонент Content может получить прямой доступ к store.
9. Импортируем нужные модули
В файле src/component/Content.js внесем изменения:
Метод connect() поможет определить, какие свойства и действия будут у нашего компонента, а также связать компонент и store.
Также мы импортировали те действия, которые относятся к нашему компоненту.
10. Определяем свойства и действия для компонента
Функция mapStateToProps() определяет, какие свойства будут связаны между store и компонентом.
Функция mapDispatchToProps() определяет, какие действия будут доступны компоненту.
11. Связываем всё вместе
С помощью метода connect() связываем свойства и действия с нашим компонентом:
Метод connect() определяет, какие свойства и действия будут у нашего компонента.
12. Завершение
Итак, настало время подвести итог.
Краткий алгоритм работы с React & Redux будет следующим:
При создании статьи были использованы следующие источники:
Основы Redux для начинающих
Что такое Redux?
Redux — это инструмент для управления состоянием приложения. Построен на принципах технологии Flux и функционального программирования. Создан компанией FaceBook, но вопреки распространенному мнению может использоваться не только в связке с React, но также и с другими фреймворками/библиотеками.
Flux — это тип архитектуры и набор паттернов проектирования веб-приложений. Подробнее на Wiki
Redux использует методологию flux. Она состоит из 4 понятий:
В React по умолчанию нет какого-то глобального state (состояния), которое было бы доступно во всем приложении. Вы можете только сохранять данные в рамках одного компонента. К примеру, у вас есть интернет магазин и в нем есть корзина с товарами. Если работать только со стейтом компонента Корзина, то вам эти данные будут недоступны в других компонентах. Также например, у вас есть иконка корзины в углу экрана, которая должна показывать количество товара, которые пользователь добавил туда. Так вот средствами чисто React, это будет сложно реализовать.
Вот именно поэтому есть такие библиотеки как Redux, для хранения всех данных приложения в одном месте и удобного их обновления.
Основные понятия Redux
Как я уже писал выше, основные понятия редакса — actions, dispatcher, store.
Store — это состояние веб-компонента, которое хранит в себе всю информацию (или ту которую вы решили сохранить в него). В дальнейшем стор будет доступен из любого компонента вашего приложения.
Action — действие, описывает что нужно сделать. Согласно принципам функционального программирования, мы не можем изменять объект напрямую, поэтому нам нужны экшены, чтобы передать их в диспатчер и «сказать», что нужно сделать.
Dispatcher — сообщает хранилищу о каком-то действии (action) и передает ему обновленную информацию.
Теперь когда мы разобрали основные понятия, давайте посмотрим как работает Redux:
Схема работы Redux
Компонент генерирует действие (action), диспатчер сообщает об этом хранилищу (store), хранилище изменяет состояние и данные передаются в компонент (View).
Есть еще одно понятие в Redux это reducer (редюсер). Редюсер — это чистая функция, которая принимает как аргумент хранилище и экшен. Основные правила редюсеров:
Более подробно про чистые функции можно прочитать тут.
Простой пример использования Redux
Теперь на простом практическом примере разберем как работать с Redux.
Мы сделаем простое приложение ToDo, которое даст возможность создавать свои таски с сохранением их в store. Это будет простое приложение для примера, основной упор сделан на работу с Redux.
Итак, есть 2 варианта, вы можете скачать стартовый проект и просто запустить установку, или пошагово пройти и создать проект со старта.
Установка и настройка проекта
Чтобы создать приложение заново, открываем командную строку или Git Bash
Далее заходим в папку проекта и устанавливаем Redux и пакет для Реакта — react-redux
Если вы скачали архив с уже готовым приложением, тогда его нужно распаковать, войти в папку с приложением и в командной строке/терминале запустить команду:
Теперь, чтобы запустить наш проект нужно воспользоваться следующей командой:
Проект будет собран и запущен, автоматически откроется вкладка в браузере
Результат выполнения команды npm run start
Для того, чтобы не верстать всё заново, мы используем Bootsrap. Давайте его установим.
Также подключим стили в файле src/index.js
Создание базовой структуры для хранилища
Теперь давайте сделаем базовую структуру для Redux. Создадим папку src/store, а в ней 4 файла
Сначала определим какие типы экшенов нам нужны в файле actionTypes.js. Можно типы и не определять, но в дальнейшем это даст нам возможность сократить время на дебагинг, если вдруг понадобится изменить имя экшена, они все находятся в одном месте, что тоже удобно. Если нам понадобится экшен в другом месте, нам достаточно будет импортировать его в другом модуле.
В нашем приложении, например, нам нужен будет экшен в 2 файлах: actions и reducer. Создадим файл actionTypes.js и в нем определим наши типы:
В файле store/actions.js мы опишем все экшены, которые нам потребуются для приложения:
Выше вы видите типичную структуру экшена: это функция, которая возвращает объект с двумя свойствами:
Теперь давайте рассмотрим функцию редюсер (store/reducer.js):
Тут мы импортируем наши типы экшенов, затем определяем переменную для того, чтобы задавать ID каждому новому таску.
Сам reducer принимает в качестве аргументов state (или равняется пустому массиву) и экшен. Далее мы проверяем тип екшена и в зависимости от этого производим определенные манипуляции со стейтом.
Давайте размеберем на примере экшена TASK_ADD. При добавлении нового таска, нам необходимо сделать копию текущего стейта и добавить к нему новый таск
Далее на основании этого редюсера нам нужно создать store с помощью функции createStore. Создадим файл store.js с таким содержимым:
* У меня также вторым параметром добавлена след. строка
Это для работы плагина для Chrome — Redux DevTools. Удобный плагин для дебагинга.
Теперь после создания store, мы можем использовать его в любом модуле нашего приложения.
Основные методы для работы со store
Redux в функциональных компонентах (хуки)
Наше приложение будет построено при помощи функциональных компонентов и хуков, поэтому мы немного рассмотрим какие хуки предоставляет нам Redux для работы в таких компонентах.
. В документации редакса говорится о том, что лучше этот хук не использовать часто, а лучше пользоваться useSelector()
Теперь давайте вернемся к нашему приложению. В index.js нам нужно обернуть наше приложение в компонент Provider и передать ему store через пропсы. Store мы создали в файле store/store.js
Следующим шагом давайте создадим папку, в которой будем хранить наши компоненты и назовем ее componetns. В ней создадим 3 файла:
Для начала импортируем хук useDispatch(), т.к. в этом компоненте мы будем диспатчить 2 экшена: выполнение таска (toggleTask) и удаление (removeTask). В компоненте у нас есть событие onChange — при клике на этот компонент мы будем диспатчить экшен для переключения состояния таска, ну а при клике кнопки удалить — диспатчим экшен для удаления таска из нашего стора.
Далее файл src/components/TaskList.js
Теперь последний компонент AddNewTask.js
Тут у нас будет 2 обработчика handleTaskTitleChange() и handleTaskSubmit(). Последний будет диспатчить экшен для создания нового таска. В идеале, тут бы еще добавить проверку на пустую строку и обрезать лишние пробелы, но у нас не об этом сейчас 🙂
Теперь остался заключительный шаг, изменить файл scr/App.js и добавить немного стилей
В итоге у нас получилась вот такое приложение
Итоговое приложение
Redux на практике: осваиваем действия в приложении
Хватит ждать, пора действовать! Изучаем действия в приложении на Redux, сразу применяя теоретический материал на практике.
Дизайн макета обновлён. При нажатии на любую из кнопок для обновления состояния пользователь чувствует взаимодействие с приложением:
Пример работы в GIF
Что есть действие в Redux?
При посещении банка кассир узнаёт о вашем намерении снять деньги. WITHDRAWAL_MONEY – оно же является действием. Чтобы получить деньги, нужно взаимодействовать с кассиром.
В отличие от setState(), единственный способ обновить состояние в Redux – сообщить об этом reducer:
На самом деле type не может в подробностях сообщить, что вы хотите сделать. Redux не знает, сколько денег нужно снять. Так будет правильнее:
Представим, что номер банковского счета не нужно сообщать, и тогда этой информации становится достаточно.
Вы не можете серьезно модифицировать функцию type. Дополнительная информация задаётся в payload:
Обработка данных в редукторах
Мы говорили, что reducer принимает два значения – state и action. Простой reducer выглядит так:
Чтобы reducer обработал функцию, нужно задать ему инструкцию, используя switch:
В данном примере switch запустит механизм исполнения действия в приложении. Допустим, у вас было 2 кнопки:
При нажатии на первую кнопку приложение переходит в состояние isOpen:
А при нажатии на вторую обновляется поле isClicked:
В Redux не получится использовать setState(): сначала нужно сообщить о действии.
В Redux-приложении всё проходит через редуктор. Поэтому мы ввели поле action.type, чтобы reducer определял нужное действие:
Важно обрабатывать каждый тип по отдельности. Для этого и нужен switch.
Обработка действия в приложении
Для обновления состояния приложений нужно сообщать action, неважно каким способом. При каждом нажатии кнопки нам нужно отправить действие:
Посмотрите, как это будет работать с каждой из кнопок.
React
React-redux
Все действия имеют одно и то же поле type. Как в банке: пополнение счёта производилось бы на разные суммы. Общая функция DEPOSIT_MONEY, разное количество amount.
Дублирование кода
Давайте уменьшим количество повторяющегося кода. Для этого в Redux есть Action Creators. Это функции, которые создают объекты-действия. Например, в нашем случае можно создать функцию setTechnology, принимающую аргумент «текст»:
Объединяем пройденное
Итак, когда вы приходите в банк, кассир сидит за столом и ожидает клиентов, а сейф с деньгами ждёт своего часа в соседней комнате. В Redux каждый участник цепочки (reducer, action, store) тоже находится на своём месте – в отдельной папке. Обычно создают 3 папки:
В каждой из папок создаем файл index.js, что позволит начать работу каждой функции. Теперь будем работать с нашим приложением из статьи.
store/index.js
Это работает так же, как и ранее. Отличие в том, что хранилище создаётся в отдельном index.js файле. Если нам нужен store, будем писать:
App.js
В чем отличия? В четвертой строке хранилище импортируется из собственной «комнаты». Кроме того, здесь есть компонент, который отвечает за работу кнопок.
Следующая особенность в том, что приложение возвращает массив. Это стало доступно в React 16.
Так это работает для компонента App.js.
Реализация ButtonGroup проще:
ButtonGroup не имеет состояния. Он просто принимает массив названий технологий и с помощью map генерирует button для каждого элемента. В данном примере передаётся массив [«React», «Elm», «React-redux»]. У кнопок есть несколько атрибутов:
Сгенерированная кнопка выглядит так:
Прямо сейчас все отображается правильно, но при нажатии на кнопку пока ничего не происходит.
Так происходит потому, что мы не настроили обработчик кликов. Внутри функции render определим действие для onClick:
Хорошо. Теперь определим dispatchBtnAction.
Не забудьте, что основная цель обработчика – послать действие на обработку. Если нажать на кнопку «React», произойдет следующее действие:
А если нажать на «React-Redux»:
Вот так выглядит функция dispatchBtnAction:
Имеет ли смысл код выше?
e.target.dataset.tech получит атрибут данных с кнопки data-tech. Таким образом, константа tech будет хранить текст кнопки. store.dispatch() – способ
отправки действия в приложении на Redux, а setTechnology() – генератор действий, написанный нами ранее. То есть store.dispatch принимает, а setTechnology – создаёт.
Код на изображении ниже должен помочь разобраться в происходящем:
Что происходит после отправки действия?
Для начала несложный вопрос. Что происходит после нажатия кнопки (и отправки действия)? Какой участник появляется в цепочке Redux?
Правильный ответ – кассир. Здесь действия после отправки проходят через редуктор. Чтобы показать это, залоггируем все действия в приложении, проходящие через него:
reducers/index.js
Reducer возвращает начальное состояние. Через console.log() можно посмотреть, что происходит при нажатии на кнопку.
Действия учитываются при нажатии, значит, все проходит через редукторы, как мы и говорили.
Но есть один нюанс. При запуске приложения учитывается лишнее действие:
Это обычная инициализация, не оказывающая негативного влияния на работу программы.
Создание счётчика редуктора
До текущего момента мы писали приложение, которое ничего не делает. Как кассир, который не умеет делать WITHDRAW_MONEY. Что мы хотим от редуктора? При создании хранилища мы передали initialState в createStore.
Когда пользователь нажимает на любую из кнопок, редуктор должен модифицировать состояние:
Цель последнего действия – обновление состояния. Будем использовать switch для обработки разных действий:
Но теперь в фокусе switch будет action.type. Почему? В качестве операций, которые может выполнить кассир – снять наличные, внести на счет, etc. Наш редуктор выполняет действие SET_TECHNOLOGY, но позднее могут быть и другие. Этот единственный case будет отвечать за переключение технологии на любое значение.
Не забывайте, что во всех прочих случаях не стоит выполнять никаких действий. Нужно просто вернуть текущее состояние state.
Кассир (редуктор) понимает, что вы хотите сделать, но не возвращает никакого ответа. Исправим это:
Что только что произошло – объясняем ниже.
Никогда не меняйте состояние внутри редукторов
Первое, что хочется сделать – изменить state и вернуть его (код ниже). Если вы уже знакомы с хорошим стилем в React, то знаете, что это ошибка:
Редуктор и вернул вот это:
Вместо изменения состояния мы возвращаем новый объект. Он имеет все свойства предыдущего, но находится в другом пространстве.
Кроме того, редукторы должны быть чистыми функциями без вызовов API, обновления значений. Теперь воображаемый кассир выдаёт нам деньги. Попробуем нажимать на кнопки. Работает? Нет! По крайней мере, текст не обновляется. В чём дело?
Обновление хранилища
После снятия денег обычно приходит сообщение о совершении операции. В Redux тоже следует настроить получение уведомлений об успешном обновлении состояния.
В каждом хранилище есть метод store.subscribe(). Он вызывается всякий раз, когда изменяется состояние.
Итак, нам нужно обновить элемент на странице. Для этого используем render.
Посмотрим, как это работает в index.js:
Приложение берёт компонент и отображает его в DOM.
Используя принципы ES6, функцию можно упростить.
После каждого успешного обновления будет повторно отображаться с новыми значениями состояния: