React reducer что это
Краткое руководство по Redux для начинающих
Авторизуйтесь
Краткое руководство по Redux для начинающих
Библиотека Redux — это способ управления состоянием приложения. Она основана на нескольких концепциях, изучив которые, можно с лёгкостью решать проблемы с состоянием. Вы узнаете о них далее, в этом руководстве по Redux для начинающих.
Примечание Вы читаете улучшенную версию некогда выпущенной нами статьи.
Содержание:
Когда нужно пользоваться Redux?
Redux идеально использовать в средних и крупных приложениях. Им стоит пользоваться только в случаях, когда невозможно управлять состоянием приложения с помощью стандартного менеджера состояний в React или любой другой библиотеке.
Простым приложениям Redux не нужен.
Использование Redux
Разберём основные концепции библиотеки Redux, которые нужно понимать начинающим.
Неизменяемое дерево состояний
В Redux общее состояние приложения представлено одним объектом JavaScript — state (состояние) или state tree (дерево состояний). Неизменяемое дерево состояний доступно только для чтения, изменить ничего напрямую нельзя. Изменения возможны только при отправке action (действия).
Действия
Действие (action) — это JavaScript-объект, который лаконично описывает суть изменения:
Типы действий должны быть константами
В простом приложении тип действия задаётся строкой. По мере разрастания функциональности приложения лучше переходить на константы:
и выносить действия в отдельные файлы. А затем их импортировать:
Генераторы действий
Генераторы действий (actions creators) — это функции, создающие действия.
Обычно инициируются вместе с функцией отправки действия:
Или при определении этой функции:
Редукторы
При запуске действия обязательно что-то происходит и состояние приложения изменяется. Это работа редукторов.
Что такое редуктор
Редуктор (reducer) — это чистая функция, которая вычисляет следующее состояние дерева на основании его предыдущего состояния и применяемого действия.
Чистая функция работает независимо от состояния программы и выдаёт выходное значение, принимая входное и не меняя ничего в нём и в остальной программе. Получается, что редуктор возвращает совершенно новый объект дерева состояний, которым заменяется предыдущий.
Чего не должен делать редуктор
Редуктор — это всегда чистая функция, поэтому он не должен:
Поскольку состояние в сложных приложениях может сильно разрастаться, к каждому действию применяется не один, а сразу несколько редукторов.
Симулятор редуктора
Упрощённо базовую структуру Redux можно представить так:
Состояние
Список действий
Редуктор для каждой части состояния
Редуктор для общего состояния
Хранилище
Хранилище (store) — это объект, который:
Хранилище в приложении всегда уникально. Так создаётся хранилище для приложения listManager:
Хранилище можно инициировать через серверные данные:
Функции хранилища
Прослушивание изменений состояния:
Поток данных
Поток данных в Redux всегда однонаправлен.
Передача действий с потоками данных происходит через вызов метода dispatch() в хранилище. Само хранилище передаёт действия редуктору и генерирует следующее состояние, а затем обновляет состояние и уведомляет об этом всех слушателей.
Советуем начинающим в Redux прочитать нашу статью о других способах передачи данных.
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), который создаёт новые компоненты.
Редюсеры (Reducers)
Редюсеры определяют, как состояние приложения изменяется в ответ на экшены, отправленные в стор. Помните, что экшены только описывают, _что произошло, но не описывают, как изменяется состояние приложения.
Проектирование структуры состояния (State)
В Redux все состояние приложения хранится в виде единственного объекта. Подумать о его структуре перед написанием кода — довольно неплохая идея. Каково минимальное представление состояния Вашего приложения в виде объекта?
Для нашего todo-приложения, мы хотим хранить две разные сущности:
Часто вы будете понимать, что вам нужно хранить некоторые данные, а также некоторые состояния пользовательского интерфейса в дереве состояний. Это нормально, только старайтесь такие данные не смешивать с данными, которые описывают состояние UI.
Заметка об отношениях
Обработка экшенов
Теперь, когда мы определились с тем, как должны выглядеть наши объекты состояния (state objects), мы готовы написать редюсер для них. Редюсер (reducer) — это чистая функция, которая принимает предыдущее состояние и экшен (state и action) и возвращает следующее состояние (новую версию предыдущего).
Мы рассмотрим способы выполнения сайд-эффектов в продвинутом руководстве. На данный момент просто запомните, что редюсер должен быть чистым. Получая аргументы одного типа, редюсер должен вычислять новую версию состояния и возвращать ее. Никаких сюрпризов. Никаких сайд-эффектов. Никаких обращений к стороннему API. Никаких изменений (mutations). Только вычисление новой версии состояния.
Исходя из вышенаписанных принципов, давайте начнем писать редюсер, постепенно обучая его понимать экшены (actions), которые мы описали чуть раньше.
Мы начнем с определения начального состояния (initial state). В первый раз Redux вызовет редюсер с неопределенным состоянием( state === undefined ). Это наш шанс инициализировать начальное состояние приложения:
Использование синтаксиса аргументов по умолчанию из ES6 для более компактного написания — просто аккуратный трюк:
Мы возвращаем предыдущую версию состояния ( state ) в default ветке. Очень важно возвращать предыдущую версию состояния ( state ) для любого неизвестного/необрабатываемого экшена ( action ).
Обратите внимание на Object.assign
Object.assign() это часть ES6, но этот метод не поддерживается старыми браузерами. Вам нужно будет использовать использовать полифилл, плагин для Babel, либо хелпер из другой библиотеки, к примеру _.assign() из lodash.
Обратите внимание на switch и шаблон (boilerplate)
Обрабатываем больше экшенов
Ну и наконец, имплементация обработчика для экшена TOGGLE_TODO не должна стать для Вас большим сюрпризом:
Разделение редюсеров
Вот так выглядит наш код на данный момент. Выглядит излишне многословным:
Есть ли способ облегчить понимание? Кажется, что todos и visibilityFilter обновляются совершенно независимо. Иногда поля состояния (state fields) зависят от других полей и требуется большая связанность, но в нашем случаем мы безболезненно можем вынести обновление todos в отдельную функцию:
Ниже нашего импорта давайте использовать ES6 Object Destructuring, чтобы объявить SHOW_ALL :
Обратите внимание на то, что каждый из этих дочерних редюсеров управляет только какой-то одной частью глобального состояния. Параметр state разный для каждого отдельного дочернего редюсера и соответствует той части глобального состояния, которой управляет этот дочерний редюсер.
Уже выглядит лучше! Когда приложение разрастается, мы можем выносить редюсеры в отдельные файлы и поддерживать их совершенно независимыми, что дает нам возможность управлять различными разделами наших данных.
Обратите внимание, что это полностью эквивалентно такому коду:
Вы также можете назначать им разные ключи или вызывать функции по-разному. Есть два совершенно равноценных способа писать комбинированные редюсеры:
Все, что делает combineReducers() — это генерирует функцию, которая вызывает ваши редюсеры c частью глобального состояния, которая выбирается в соответствии с их ключами, и затем снова собирает результаты всех вызовов в один объект. Тут нет никакой магии. И, как и другие редюсеры, combineReducers() не создает новый объект, если все предоставленные ему редюсеры не изменяют состояние.
Заметка для сообразительных пользователей синтаксиса ES6
Т.к. combineReducers ожидает на входе объект, мы можем поместить все редюсеры верхнего уровня в разные файлы, экспортировать каждую функцию-редюсер и использовать import * as reducers для получения их в формате объекта, ключами которого будут имена экспортируемых функций.
Поскольку import * — это все еще новый синтаксис, мы не используем его нигде в документации во избежание путаницы, но вы можете случайно наткнуться на него в каких-нибудь примерах кода из сообщества.
Исходный код
reducers.js
Следующие шаги
Далее мы изучим как создать Redux-стор, которое содержит состояние и заботится о вызове редюсеров, когда вы отправляете экшен.
Введение в Redux: основные понятия
Redux – это контейнер с предсказуемым состоянием для приложений JavaScript и очень ценный инструмент для управления состоянием приложения. Также это популярная библиотека для управления состоянием в приложениях React, но ее можно использовать и с Angular, Vue.js и со старым добрым JavaScript.
Главная сложность Redux заключается в том, что многие разработчики не понимают, когда его использовать. Чем больше и сложнее становится приложение, тем выше вероятность того, что Redux будет вам полезен. Если вы начинаете работать над приложением и ожидаете, что в скором будущем оно существенно вырастет, вы можете сразу использовать Redux: так по мере изменения и масштабирования приложения вы сможете легко внедрять все новые функции, избегая рефакторинга большого количества готового кода.
В этом кратком руководстве по Redux мы рассмотрим основные понятия: reducer-ы, action-ы, action creator-ы and store. На первый взгляд это может показаться сложной темой, но на самом деле основные понятия Redux довольно просты.
Что такое reducer?
reducer – это чистая функция, которая принимает в качестве аргументов предыдущее состояние и action и возвращает новое состояние. Action – это объект, задающий тип и (опционально) нагрузку:
function myReducer(previousState, action) => <
// use the action type and payload to create a new state based on
// the previous state.
return newState;
>
Reducer-ы определяют, как изменяется состояние приложения в ответ на action-ы (действия), отправленные в store.
Поскольку reducer-ы являются чистыми функциями, мы не меняем передаваемые им аргументы, не выполняем вызовы API или маршрутизацию и не вызываем нечистые функции, такие как Math.random() или Date.now().
Если ваше приложение имеет несколько частей состояния, вы можете использовать несколько reducer-ов. Например, каждая важная функция в вашем приложении может иметь собственный reducer. Reducer-ы сосредоточены на значении состояния.
Что такое action?
Action-ы – это простые объекты JavaScript, которые представляют полезную нагрузку, отправляющую данные из приложения в store. Action-ы принимают тип и опционально полезную нагрузку (type и payload).
Большинство изменений в приложении, использующем Redux, начинаются с события, которое прямо или косвенно запускается пользователем. События – это, например, нажатие кнопки, выбор элемента из раскрывающегося меню, наведение курсора на определенный элемент или запрос AJAX, который только что вернул какие-то данные. Даже начальная загрузка страницы может быть поводом для отправки action-а. Действия часто отправляются с помощью action creator-а.
Что такое action creator?
В Redux action creator – это функция, которая возвращает объект action. Action creator может показаться лишним компонентом, но он повышает портативность и упрощает тестирование. Объект action, возвращаемый action creator-ом, отправляется всем различным reducer-ам в приложении.
В зависимости от action-а reducer-ы могут выбрать возврат новой версии своего фрагмента состояния. Затем возвращенная часть состояния передается по конвейеру в состояние приложения, которое затем возвращается в приложение React, а затем – вызывает повторный рендеринг всех его компонентов.
Допустим, пользователь нажимает кнопку, после чего мы вызываем action creator, который представляет собой функцию, возвращающую объект action. Этот объект содержит аргумент type, описывающий тип только что запущенного действия.
Вот пример action creator-а:
А вот простой reducer, работающий с action-ом типа ADD_TODO:
export default function(state = initialState, action) <
switch (action.type) <
case ‘ADD_TODO’:
const newState = [. state, action.payload];
return newState;
// Deal with more cases like ‘TOGGLE_TODO’, ‘DELETE_TODO’.
default:
return state;
>
>
Все reducer-ы обработали action. Reducer-ы, которые не заинтересованы в этом конкретном типе действия, просто возвращают то же состояние, а заинтересованные редукторы возвращают новое состояние. После этого все компоненты уведомляются об изменениях состояния. Затем они будут повторно отображаться с новыми свойствами:
Комбинирование reducer-ов
Redux предоставляет функцию под названием combReducers, которая выполняет две задачи:
Что такое store?
Мы уже много раз упоминали store, но еще неговорили о том, что он из себя представляет.
В Redux store – это объект, объединяющий action-ы (которые представляют то, что произошло) и reducer-ы (которые обновляют состояние в соответствии с этими action-ами). В приложении Redux может быть только один store.
У store есть несколько обязанностей:
По сути, все, что нам нужно для создания store, – это reducer-ы. Мы упомянули функцию combReducers, которая может объединить несколько reducer-ов в один. Теперь, чтобы создать store, мы импортируем combReducers и передадим его createStore:
import < createStore >from ‘redux’;
import todoReducer from ‘./reducers’;
const store = createStore(todoReducer);
Затем мы отправляем action-ы приложения, используя метод dispatch:
Поток данных в Redux
Одним из многих преимуществ Redux является то, что все данные в приложении следуют одному и тому же шаблону жизненного цикла. Логика приложения более предсказуема и проста для понимания, поскольку архитектура Redux строго следует однонаправленному потоку данных.
Основные этапы жизненного цикла данных в Redux
export default const currentTask(state = <>, action) <
// deal with this piece of state
return newState;
>;
export default const todos(state = [], action) <
// deal with this piece of state
return newState;
>;
export default const todoApp = combineReducers( <
todos,
currentTask,
>);
При запуске action-а todoApp вызовет оба reducer-а и объединит оба набора результатов в одно дерево состояний:
return <
todos: nextTodos,
currentTask: nextCurrentTask,
>;
Заключение
Redux предлагает очень мощный шаблон управления состоянием приложения. Здесь мы только вкратце затронули очень много важных аспектов работы Redux – не пугайтесь, если вы еще не совсем разобрались, как все части сочетаются друг с другом. Вполне естественно, что вам потребуется немного практики, чтобы привыкнуть к работе с этим инструментом.
Чтобы узнать больше, ознакомьтесь с этими ресурсами:
В будущем мы планируем рассмотреть более сложные темы, такие как обработка асинхронных событий с помощью Redux-Saga или Redux Thunk.
С 0 до 1. Разбираемся с Redux
Когда вышла версия 1.0 Redux, я решил потратить немного времени на серию рассказов о моем опыте работы с ним. Недавно я должен был выбрать “реализацию Flux” для клиентского приложения и до сих пор с удовольствием работаю с Redux.
Почему Redux?
Redux позиционирует себя как предсказуемый контейнер состояния (state) для JavaScript приложений. Редакс вдохновлен Flux и Elm. Если вы раньше использовали Flux, я советую прочитать, что Redux имеет с ним общего в разделе «Предшественники» новой (отличной!) документации.
Redux предлагает думать о приложении, как о начальном состоянии модифицируемом последовательностью действий (actions), что я считаю действительно хорошим подходом для сложных веб-приложений, открывающим много возможностей.
Конечно, вы можете найти больше информации о Redux, его архитектуре и роли каждого компонента в документации.
Создаем список друзей с React и Redux
Сегодня мы сфокусируемся на пошаговом создании вашего первого приложения, использующего Редакс и Реакт: создадим простой список друзей с нуля.
Вы можете найти готовый код здесь.
Для кого?
Эта статья написана для людей, не имеющих опыта работы с Redux. Опыт разработки с Flux также не обязателен. Я буду давать ссылки на документы, когда мы будем сталкиваться с новыми понятиями.
1. Установка
Автор Redux, Даниил Абрамов, создал отличную сборку для разработки с React, Webpack, ES6/7 и React Hot Loader, которую вы можете найти здесь.
Есть сборки уже с установленным Redux, но, я думаю, важно понять роль каждой библиотеки.
Теперь вы можете открыть приложение по адресу http://localhost:3000. Как вы видите, «hello world» готов!
1.1 Добавим redux, react-redux и redux-devtools
Нам нужно установить три пакета:
1.2 Структура директорий
Хотя то, что мы будем делать, довольно просто, давайте создадим структуру директорий как для реального приложения.
Мы будет видеть более детально роль каждой из директорий, когда будем создавать приложение. Мы переместили App.js в директорию containers, так что нужно будет настроить импорт statement в index.js.
1.3 Подключаем Redux
Нам нужно включить devtools только для окружения разработки, так что модифицируем webpack.config.js как здесь:
Мы делаем здесь две вещи. Мы переопределяем createStore используя созданную функцию, которая позволяет нам применять множественные store enhancers, таких как devTools. Мы также включаем функцию renderDevTools, которая рендерит DebugPanel.
Сейчас нам нужно модифицировать App.js для соединения с redux. Для этого мы будем использовать Provider из react-redux. Это сделает наш экземпляр хранилища доступным для всех компонентов, которые располагаются в Provider компоненте. Не нужно беспокоится о странно выглядящей функции, ее цель использовать “контекст” функции Реакта для создания хранилища, доступного для всех детей (компонентов).
Для создания хранилища мы используем createStore функцию, которую мы определили в devTools файле, как map всех наших редьюсеров.
В нашем приложении App.js — внешняя обертка для Redux и FriendListApp — корневой компонент для нашего приложения. После создания простого ‘Hello world’ в FriendListApp.js, мы можем наконец запустить наше приложение с redux и devTools. Вы должен получить это (без стилей).
Хотя это просто ‘Hello world’ приложение, у нас включен Hot Reloading, т.е. вы можете модифицировать текст и получать автоматическое обновление на экране. Как вы можете видеть, devtools справа показывает пустые хранилища. Заполним их!
2. Создаем приложение
Теперь, когда сделаны все настройки, мы можем сфокусироваться на самом приложении.
2.1 Действия и генераторы действий
Как вы можете видеть, это очень выразительный путь определения области действий нашего приложения, которое будет позволять нам добавлять друзей, отмечать их как «избранных» или удалять их из нашего списка.
Генераторы действий — функции, которые создают действия. В Redux генераторы действий являются чистыми функциями, что делает их портативными и простыми для тестирования, т.к. они не имеют сайд-эффектов.
Мы поместим их в папку действий, но не забывайте, что это разные понятия.
Как видите, действия довольно минималистичны. Чтобы добавить элемент, мы сообщаем все свойства (здесь мы имеем дело только с name), а для других мы ссылаемся на id. В более сложном приложении, мы, возможно, имели бы дело с асинхронными действиями, но это тема для другой статьи…
2.2 Редьюсеры
Мы, для начала, определяем вид состояния нашего приложения в initialState :
Состоянием может быть все, что мы захотим, мы можем просто сохранить массив друзей. Но это решение плохо масштабируется, так что мы будем использовать массив id и map друзей. Об этом можно почитать в normalizr.
Теперь нам нужно написать актуальный редьюсер. Мы воспользуемся возможностями ES6 для задания аргументов по умолчанию для обработки случаев, когда состояние не определено. Это поможет понять как записать редьюсер, в данном случае я использую switch.
Если вы не знакомы с синтаксисом ES6/7, то возможно вам будет трудно это прочесть. Т.к. нам нужно вернуть новое состояние объекта, как правило используют Object.assign или Spread operator.
Что здесь происходит: мы определяем новый id. В реальном приложении мы, возможно, возьмем его с сервера или, как минимум, убедимся, что он уникальный. Затем мы используем concat чтобы добавить этот новый id в наш id-лист. Concat добавит новый массив и не изменит оригинальный.
Как вы можете видеть, несмотря на синтаксис, который может сначала смутить, логика проста. Вы задаете состояние и получаете назад новое состояние. Важно: ни в одной точке этого процесса не изменять предыдущее состояние.
Окей, давайте вернемся и создадим редьюсеры для двух других действий:
Вы также можете заметить, что spread оператор позволяет нам манипулировать только теми состояниями, которое нам нужно изменить.
Redux не важно, как вы храните данные, так что можно использовать Immutable.js.
Теперь вы можете поиграть с хранилищем минуя интерфейс, путем вызова dispatch вручную в нашем App.js.
Вы увидите в devTools действия, с ними можно поиграть в реальном времени.
3. Создаем интерфейс
Т.к. этот урок не об этом, я пропустил создание React-компонентов и сфокусировался только на Redax. Мы имеем три простых компонента:
В Redux считается хорошей практикой делать по возможности большинство компонентов “глупыми”. Т.е. чем меньше компонентов связаны с Redux, тем лучше.
Здесь FriendListApp будет единственным “умным” компонентом.
Это часть нового синтаксиса ES7, называемая декоратор. Это удобный способ вызова функции высшего порядка. Будет эквивалентна connect(select)(FriendListApp); где select — функция, которая возвращает то, что мы здесь сделали.
То, что случится дальше — стандартный подход для React. Мы привяжем функции к onClick, onChange или onKeyDown свойствам, чтобы обработать действия пользователя.
Если вы заинтересовались, как это сделать, ты можете посмотреть весь код.
Сейчас вы можете почувствовать магию работы redux/react приложения. Как изображено на GIF, вы логгируете все действия.
Разрабатывать удобней, когда ты можешь производить какие-то действия, находить баги, возвращаться, исправлять их и повторять уже исправленную последовательность…