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 помогает удобно смотреть изменения.