Redux — это такая база данных в программе. Она хранит внутри себя данные приложения или, как говорят, состояние. Redux отвечает только за данные и никак не связан с браузером, DOM и фронтендом в целом. Теоретически Redux можно использовать даже на бекенде в Node.js.
Redux, с точки зрения кода — это объект, внутри которого лежат данные. Он используется остальными частями приложения для их хранения, изменения и извлечения. В терминологии Redux он называется хранилищем (store), так как данные хранятся внутри.
В простейшем случае для решения подобной задачи подошел бы и обычный объект JavaScript:
// Пример состояния и его инициализации
const state = { posts: [], activePostId: null, categories: [] };
// Где-то внутри приложения
state.posts.push(/* новый пост */);
// Где-то в другой части
state.posts.map(/* логика обработки */);
Но такой подход не позволяет отслеживать изменение данных. Если какая-то часть приложения изменила их, то мы об этом не узнаем, а значит не сможем отреагировать, например перерисовав нужную часть экрана. Redux решает эту проблему. Изменение данных внутри хранилища порождает события, на которые можно подписываться и выполнять произвольную логику (обычно перерисовку экрана). Достигается это за счет того, что данные внутри Redux изменяются не напрямую, как в случае обычного объекта, а через указание "действий" (actions).
Ниже полный пример использования Redux:
import { createStore } from 'redux';
// Редьюсер – функция, которая описывает то, как изменяются данные внутри хранилища
// Она принимает на вход текущее состояние приложения и должна вернуть новое
// Именно так работает функция reducer, отсюда и название, но оно может быть любым
// Второй параметр описывает действие, с его помощью мы узнаем
// как конкретно надо обновить данные для конкретного вызова
// action — это объект, в котором обязательно есть поле type, содержащее имя действия
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default: // действие по умолчанию – возврат текущего состояния
return state;
}
};
// Создание хранилища на основе редьюсера
// Именно в этом хранилище находится состояние, которое возвращает редьюсер
const store = createStore(reducer);
// Состояние можно извлечь с помощью функции getState()
store.getState(); // 0 – так как это начальное значение состояния
// Функция subscribe позволяет подписываться на изменение состояния внутри хранилища
// Она очень похожа на addEventListener, но без указания события
// Как только меняется любая часть состояния, хранилище вызывает переданную функцию
// Здесь мы просто извлекаем состояние и печатаем его на экран
store.subscribe(() => console.log(store.getState()));
// dispatch – функция, которая вызывает редьюсер
// Редьюсер увеличивает состояние на единицу
store.dispatch({ type: 'INCREMENT' }); // 1
// Редьюсер увеличивает состояние на единицу
store.dispatch({ type: 'INCREMENT' }); // 2
// Редьюсер уменьшает состояние на единицу
store.dispatch({ type: 'DECREMENT' }); // 1
store.getState(); // 1
// Для избежания дублирования и повышения уровня абстракции, вынесем действия в функции
const increment = () => ({ type: 'INCREMENT' });
const decrement = () => ({ type: 'DECREMENT' });
store.dispatch(increment()); // 2
store.dispatch(decrement()); // 1
Единственный способ произвести изменения состояния в хранилище — это передать/отправить действие (action) в функцию dispatch()
. Действие — обычный JS-объект, в котором присутствует минимум одно свойство — type
. Никаких ограничений на содержимое этого свойства не накладывается, главное, чтобы внутри редьюсера был подходящий ему обработчик (в switch).
Сам процесс изменения состояния описан внутри редьюсера, а снаружи мы лишь говорим, какое изменение необходимо выполнить. Этот подход резко отличается от того, как мы делали в React, где изменение состояния происходит напрямую:
// Пример изменения состояния в React
class Clock extends React.Component {
handleClick() {
this.setState({ date: new Date() });
}
}
Посмотрим еще один пример с использованием массива и передачей данных через действие:
// payload - свойство внутри которого хранятся данные
const addUser = (user) => ({ type: 'USER_ADD', payload: { user } });
const reducer = (state = [], action) => { // инициализация состояния
switch (action.type) {
case 'USER_ADD': {
const { user } = action.payload; // данные
// так в редьюсере возвращаются новые данные без изменения старых
return [...state, user];
}
case 'USER_REMOVE': {
const { id } = action.payload; // данные
return state.filter(u => u.id !== id); // данные не меняются
}
default:
return state;
}
};
const user = /* ... */;
const store = createStore(reducer);
store.dispatch(addUser(user));
Несмотря на то, что ключ payload
необязательный и можно все данные складывать в объект под любыми свойствами, крайне рекомендуется так не делать. Мешать в одном объекте статически заданные ключи с динамическими плохая идея. Кроме того, в будущем мы будем использовать библиотеки, которые требуют именно такого способа работы.
Для написания самой простой версии Redux, нужно всего 7 строчек. Вот они:
// Второй параметр – начальное состояние данных внутри хранилища
const createStore = (reducer, initialState) => {
let state = initialState;
return {
dispatch: action => { state = reducer(state, action) },
getState: () => state,
}
}
Выше упоминалось, что начальное состояние задаётся в определении редьюсера:
const reducer = (state = 0, action) => { /* ... */ }
Но часто этого недостаточно. Данные могут прийти из бэкенда и их нужно прогрузить в хранилище перед началом работы. Для этого случая в Redux есть особый путь:
// Второй параметр – начальное состояние при создании хранилища
const store = createStore(reducer, initState);
// @@redux/INIT
Redux посылает специальное действие, которое нельзя перехватывать. Если редьюсер реализован правильно и содержит секцию default
в switch
, то хранилище заполнится данными из initState
. Пример:
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
const store = createStore(reducer, 100);
store.getState(); // 100
В коде выше, функция createStore()
вызовет редьюсер так: reducer(100, '@@redux/INIT')
. Затем выполнится ветка default
и состоянием хранилища станет число 100.
Подведём итог. Что главное в Redux:
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт