Введение в 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-а:

export function addTodo({ task }) {
return {
type: 'ADD_TODO',
payload: {
task,
completed: false
},
}
}
// example returned value:
// {
//   type: 'ADD_TODO',
//   todo: { task: '🛒 get some milk', completed: false },
// }

А вот простой 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-ы, которые не заинтересованы в этом конкретном типе действия, просто возвращают то же состояние, а заинтересованные редукторы возвращают новое состояние. После этого все компоненты уведомляются об изменениях состояния. Затем они будут повторно отображаться с новыми свойствами:

{
currentTask: { task: '🛒 get some milk', completed: false },
todos: [
{ task: '🛒 get some milk', completed: false },
{ task: '🎷 Practice saxophone', completed: true }
],
}

Комбинирование reducer-ов

Redux предоставляет функцию под названием combReducers, которая выполняет две задачи:

  • генерирует функцию, которая вызывает наши reducer-ы с фрагментом состояния, выбранным в соответствии с их ключом.
  • а затем снова объединяет результаты в один объект.

Что такое store?

Мы уже много раз упоминали store, но еще неговорили о том, что он из себя представляет.

В Redux store – это объект, объединяющий action-ы (которые представляют то, что произошло) и reducer-ы (которые обновляют состояние в соответствии с этими action-ами). В приложении Redux может быть только один store.

У store есть несколько обязанностей:

  • Разрешать доступ к состоянию через getState().
  • Разрешать обновление состояния через dispatch(action).
  • Хранить состояние приложения.
  • Регистрировать слушателей с помощью subscribe(listener).
  • Отменять регистрацию слушателей с помощью функции, возвращаемой subscribe(listener).

По сути, все, что нам нужно для создания store, — это reducer-ы. Мы упомянули функцию combReducers, которая может объединить несколько reducer-ов в один. Теперь, чтобы создать store, мы импортируем combReducers и передадим его createStore:

import { createStore } from 'redux';
import todoReducer from './reducers';
const store = createStore(todoReducer);

Затем мы отправляем action-ы приложения, используя метод dispatch:

store.dispatch(addTodo({ task: '📖 Read about Redux'}));
store.dispatch(addTodo({ task: '🤔 Think about meaning of life' }));
// ...

Поток данных в Redux

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

Основные этапы жизненного цикла данных в Redux

  • Событие внутри вашего приложения становится причиной вызова store.dispatch(actionCreator(payload)).
  • Хранилище Redux вызывает корневой reducer с текущим состоянием и action-ом.
  • Корневой reducer объединяет выходные данные нескольких reducer-ов в единое дерево состояний.

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 store сохраняет полное дерево состояний, возвращаемое корневым reducer-ом. Новое дерево состояний становится следующим состоянием вашего приложения.

Заключение

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

Чтобы узнать больше, ознакомьтесь с этими ресурсами:

В будущем мы планируем рассмотреть более сложные темы, такие как обработка асинхронных событий с помощью Redux-Saga или Redux Thunk.

Tags:

Добавить комментарий