
Redux withTypeScript — правильный способ строгой типизации в соответствии с Redux Toolkit
Каждый, кто использует TypeScript, видит огромную ценность строгой типизации — она безопасна и предсказуема. Я видел много проектов, в которых используются текущие версии Redux, и в них все еще много самодекларативных интерфейсов. Это огромная проблема, если что-то изменится…
Прежде чем мы начнем, я рекомендую установить Redux Toolkit с помощью npm install --save @reduxjs/toolkit, который включает в себя множество модных создателей, которые, я думаю, вам понравятся!
Шаг 1. Действия
Redux Toolkit предоставляет нам метод createAction, который создает тип действия и необязательную полезную нагрузку. Мне нравится иметь отдельные полезные данные в действии как независимый ключ, так что давайте начнем с этого помощника.
// helpers.ts
import { createAction } from '@reduxjs/toolkit';
function withPayloadType<T>() {
return (t: T) => ({ payload: t });
}
export const createActionWithPayload = <T>(action: string) =>
createAction(action, withPayloadType<T>());
export { createAction };
Затем у нас есть два отдельных метода для создателей действий — с полезной нагрузкой или без нее.
Итак, давайте приступим к нашим действиям. Мы создаем Enum для имен действий, но конечным результатом является константа, которая выглядит как перечисление, но это объект ключевой функции с нашими действиями, готовыми к отправке.
// actions.ts
import { createActionWithPayload, createAction } from './helpers';
import { ToDo, ToDoID } from './types';
enum ToDoActionName {
ADD_TODO = 'ADD_TODO',
REMOVE_TODO = 'REMOVE_TODO'
}
export const ToDoActions = {
add: createActionWithPayload<ToDo>(ToDoActionName.ADD_TODO),
remove: createActionWithPayload<ToDoID>(ToDoActionName.REMOVE_TODO)
);
Шаг 2. Редуктор
В этом суть Redux Toolkit — createReducer с builder, которые основаны на начальном типе состояния, и с помощью метода addCase предоставляют нам типы состояния и действия.
// reducer.ts
import { createReducer } from '@reduxjs/toolkit';
import { ToDo, ToDoID } from './types';
import { ToDoActions } from './actions';
interface ToDoState extends Record<ToDoID, ToDo> {}
const initialState: ToDoState = {};
export const toDoReducer = createReducer(initialState, builder =>
builder
.addCase(ToDoActions.add, (state, { payload }) => ({
...state,
[payload.id]: payload
}))
.addCase(ToDoActions.remove, (state, { payload }) => {
const newState = { ...state };
delete newState[payload];
return newState;
})
);
Шаг 3. Магазин
Здесь нам нужно сделать 3 вещи:
- Создать магазин
- Получить тип состояния магазина
- Добавьте некоторые усилители, такие как Redux devtools.
// store.ts
import { createStore, combineReducers, compose } from 'redux';
import { toDoReducer } from './reducer';
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducer = combineReducers({
todos: toDoReducer
});
export type StoreState = ReturnType<typeof reducer>
export const store = createStore(reducer, composeEnhancers());
Шаг 4. Селекторы
И сейчас у нас простая ситуация с проверкой типов — у нас динамический тип StoreState, поэтому будет очень легко создавать новые селекторы, если вы поставите новый редуктор или если вы измените текущий редуктор.
// selectors.ts
import { StoreState } from './store';
import { ToDo, ToDoId } from './types';
export const getToDo = (state: StoreState, id: ToDoId): ToDo => state.todos[id];
Резюме
Как видите, это очень просто, и прямо сейчас вы можете быть уверены, что все типы очень сильно связаны друг с другом, поэтому влияние изменения каждого типа будет очень легко распознать.