Redux — для чего он нужен
Все взаимодействие нашего приложения происходит в интерфейсе. Но хочется отделить бизнес-логику, чтобы было проще разрабатывать и дебажить.
Одна из архитектур для работы с данными Flux. Мы делаем действие из интерфейса, в действии читаем логику и обновляем состояние. А интерфейс всегда отображает текущее состояние.
Redux — это реализация архитектуры Flux.
Мы создаем store с помощью функции createStore
const store = createStore(reducer, middlewares);
reducer — это функция, которая обновляет стор в зависимости от экшена.
middlewares — это промежуточные слои, прежде чем экшн дойдет до редьюсера.
Дальше стор можно диспатчить и смотреть текущее состояние
// В диспатч обязательно надо передать type, тип экшена,
// чтобы редьюсер мог обновить состояние по этому типу
// payload — это дополнительные данные
store.dispatch({ type: 'myAction', payload: { s: 2 } });store.getState(); // Вернет текущее состояние
В редьюсере важно всегда возвращать новый объект, чтобы не было неочевидных багов, т.к. интерфейс отображает текущее состояние. После того, как новое состояние вернется из редьюсера, реакт обновится.
const reducer = (state = {}, { type, payload }) => {
switch (type) {
case 'myAction': {
return {
...state,
result: 2 + payload.s,
};
}
default: {
return state;
}
}
};Редакс подключается к реакту также, как мы подключаем контекст
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const store = createStore(() => ({}));
const App = () => (
<Provider store={store}>
<h1>Привет, это 0xLDev</h1>
</Provider>
);
Чтобы использовать состояние или диспатчить экшены, используем хуки
import { useDispatch, useSelector } from 'redux';
import { getFunc } from './actions';
const Component = () => {
const dispatch = useDispatch();
const name = useSelector((state) => state.name);
return (
<button onClick={() => dispatch(getFunc)}>{name}, нажми на меня!</button>
);
};Для асинхронных запросов в экшенах используем библиотеку redux-thunk. Она позволяет не возвращать объект из экшена сразу, а задиспатчить его отдельно. Причем несколько раз.
export const getFunc = () => (dispatch, getState) => {
const state = getState(); // Посмотреть состояние для валидации, например.
dispatch({ type: 'fetchStart' });
fetch(url)
.then(() => dispatch({ type: 'fetchSuccess' }))
.catch(() => dispatch({ type: 'fetchFail' }))
};redux-thunk работает через middleware. Подключаем к стору так
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(reducer, applyMiddleware(thunk));MiddleWare — это функция, которая вызовется перед попаданием в редьюсер. Мы пишем свои middleware для логирования. Внутри он выглядит так
const middleware = (store) => (next) => (action) => {
// ...тут делаем все, что нам нужно
// когда будем готовы передать экшн дальше
// в редьюсер или следующий по порядку миддлвэр
// пишем так. Вызвать можно только один раз
next(action);
};Самый кайф редакса в логировании бизнес-логики. И redux-devtools-extension помогает удобно смотреть изменения.