Redux Toolkit
Пакет Redux Toolkit предназначен для облегчения работы с Redux . Он был создан, чтобы помочь решить три распространенные проблемы с Redux:
- Сложности при настройке хранилища Redux
- Необходимость добавления многих пакетов, чтобы Redux мог делать что-нибудь полезное (к примеру асинхронные действия)
- Redux требует слишком много шаблонного кода (boilerplate)
Redux Toolkit предоставляет инструменты и утилиты, облегчающие наиболее распространенные операции с хранилищем и упрощающие код проекта для работы с Redux. В данном посте рассмотрены основные моменты работы с RTK, для более глубокого изучения стоит хорошо изучить доку по Redux Toolkit.
Установка
Redux Toolkit доступен в виде пакета в NPM для использования с упаковщиком модулей или в приложении Node:
yarn add @reduxjs/toolkit
Состав пакета
- configureStore(): обертывает createStore и предоставляет упрощенные параметры конфигурации и значения по умолчанию. Он может автоматически комбинировать слайсы (slice reducers), добавлять любое промежуточное ПО (middlewares), включает по умолчанию redux-thunk и позволяет использовать расширение Redux DevTools Extension;
- createSlice(): принимает объект с редьюсером, имя слайса, начальное значение состояния и автоматически генерирует reducer слайса с соответствующими создателями действий (action creators) и типами действий (actions);
- createAsyncThunk: принимает строку типа действия (action) и функцию, которая возвращает промис, и генерирует преобразователь (thunk), который отправляет pending/fulfilled/rejected типы действий на основе этого промиса;
- createReducer(): позволяет использовать таблицу поиска (lookup table) операций для редьюсеров случая (case reducers) вместо инструкций switch. Кроме того, автоматически использует либу immer, что позволяет мутировать стейт, например state.todos[3].completed = true;
- createAction(): генерирует функцию создания экшена (action creator) для заданного типа экшена;
- combineSlices(): объединяет несколько слайсов в один редуктор и добавляет «ленивую загрузку» слайсов после инициализации;
- createEntityAdapter: генерирует набор повторно используемых редукторов и селекторов для управления нормализованными данными в хранилище;
- Утилита createSelector из библиотеки Reselect, реэкспортированная для удобства использования.
В данном посте будут рассмотрены только 3 инструмента, которых достаточно для создания хранилища, редюсеров, экшенов и асинхронной работы с Redux:
- configureStore()
- createSlice()
- createAsyncThunk
Настройка хранилища (store setup) при помощи configureStore() из 'reduxjs/toolkit'
Сначала, как обычно, создаем store. Используем для этого функцию-обёртку configureStore() из пакета тулкита, которая принимает объект с основными ключами:
- reducer - ожидает либо рутовый редюсер, либо можно передать объект с частичными редюсерами, тогда автоматически вызывается combineReducers();
- middleware - ожидает массив middlewares , автоматически вызывая applyMiddleware() и compose(), также можно вызывать getDefaultMiddleware() для добавления своих middlewares к дефолтным;
- автоматически включает расширение Redux DevTools и добавляет redux-thunk для работы с асинхронной логикой за пределами компонентов
- есть еще необязательные доп. ключи: devTools?, preloadedState?,enhancers?.
Пример создания стора с добавлением sagaMiddleware и доп. типизацией для TypeScript:
Пример создания рутового редьюсера при помощи combineReducers() из 'reduxjs/toolkit':
Подключение стора в главный компонент (пример на React 18):
Создание слайса при помощи createSlice() из 'reduxjs/toolkit'
В функцию createSlice() мы передаём объект, в котором указаны следующие ключи:
- name - строковое имя для идентификации слайса
- initialState - начальное состояние стейта
- reducers - объект, содержащий один или несколько редьюсеров
- Также есть доп. необязательные ключи - extraReducers (будем использовать для асинхронной логики совместно с createAsyncThunk), reducerPath, selectors
Использование createSlice позволяет мутировать стейт в логике редьюсера благодаря использованию либы Immer внутри. Также createSlice() анализирует функции, определенные в поле reducers, создает для каждого случая reducer и генерирует action creator`a, в качестве типа экшена используется имя слайса + имя функции редьюсера, т.е. "posts" + "increment" = "posts/increment".
После создания слайса мы можем экспортировать автоматически сгенерированные action creators для указанных редьюсеров и сам reducer для добавления в рутовый и подключения в стор.
Асинхронная работа с хранилищем при помощи createAsyncThunk() и extraReducers
Данный алгоритм является рекомендуемым подходом при работе с асинхронными запросами в RTK.
Функция createAsyncThunk() принимает строку (тип экшена) и функцию обратного вызова, которая должна возвращать промис. Для типа экшена автоматически генерируются типы действий жизненного цикла промиса на основе переданного префикса типа действия:
- pending: 'posts/fetchPosts/pending'
- fulfilled: 'posts/fetchPosts/fulfilled'
- rejected: 'posts/fetchPosts/rejected'
createAsyncThunk() не генерирует никаких функций редьюсера, поскольку не знает, какие данные мы извлекаем, как мы хотим отслеживать состояние загрузки или как необходимо обрабатывать возвращаемые данные. Поэтому для обработки этих операций нужны отдельные редьюсеры, которые также нужно указывать при создании слайса в поле extraReducers и передавать в его значение коллбек "builder callback":
Использование с типизацией при помощи кастомных redux-хуков
Для удобной работы c TS необходимо создать два кастомных хука useAppDispatch и useAppSelector, которые помогают с типизацией:
Михаил Непомнящий RTK 2.0 - youtube.com