JavaRush /Курсы /Модуль 3: React /Типизация состояния и действий в Redux с TypeScript

Типизация состояния и действий в Redux с TypeScript

Модуль 3: React
23 уровень , 3 лекция
Открыта

Типизация состояния

Состояние (state) является основным "хранилищем" данных в Redux. Типизация состояния позволяет точно знать, какие данные находятся в вашем хранилище. Для начала создадим интерфейс, описывающий структуру состояния.

Предположим, у нас есть состояние, отвечающее за данные пользователя и статус загрузки:

// types/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

export interface UserState {
  currentUser: User | null; // Пользователь может быть либо объектом, либо null
  isLoading: boolean;      // Индикатор загрузки
  error: string | null;    // Сообщение об ошибке, если есть
}

Теперь вместо магического объекта state в срезах и редьюсерах, у нас появляется четко определенная структура.

Типизация среза (slice)

С помощью createSlice мы можем легко управлять состоянием и действиями. Важно, чтобы состояние, которое мы определили ранее, было явно типизировано.

Вот пример создания среза с типизированным состоянием:

// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { UserState, User } from '../types/user';

const initialState: UserState = {
  currentUser: null,
  isLoading: false,
  error: null,
};

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    startLoading(state) {
      state.isLoading = true;
      state.error = null;
    },
    setUser(state, action: PayloadAction<User>) {
      state.currentUser = action.payload; // Тип User проверяется автоматически
      state.isLoading = false;
    },
    setError(state, action: PayloadAction<string>) {
      state.error = action.payload; // Убедимся, что ошибка — это строка
      state.isLoading = false;
    },
  },
});

export const { startLoading, setUser, setError } = userSlice.actions;
export default userSlice.reducer;

Что здесь произошло?

  1. Мы явно указали тип для состояния UserState.
  2. Использовали PayloadAction<T> для типизации полезной нагрузки payload в действиях.
  3. TypeScript гарантирует, что тип данных при вызове setUser или setError будет соответствовать ожидаемому.

Типизация действий (actions)

Действия (actions) в Redux определяют изменения, которые вы хотите внести в состояние. Типизация действует на двух уровнях:

  1. Тип полезной нагрузки payload для каждого действия.
  2. Тип всех возможных действий в целой системе.

Мы уже видели, как используется PayloadAction<T> для типизации полезной нагрузки. Давайте создадим пример вызова действия в компоненте:

// components/UserComponent.tsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store/store';
import { startLoading, setUser, setError } from '../store/userSlice';

const UserComponent: React.FC = () => {
  const user = useSelector((state: RootState) => state.user.currentUser);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(startLoading());

    // Имитация API-запроса
    setTimeout(() => {
      const fetchedUser = { id: '1', name: 'John Doe', email: 'john@example.com' };

      if (fetchedUser) {
        dispatch(setUser(fetchedUser)); // TypeScript гарантирует, что объект соответствует типу User
      } else {
        dispatch(setError('User not found'));
      }
    }, 1000);
  }, [dispatch]);

  if (!user) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h1>Welcome, {user.name}!</h1>
    </div>
  );
};

export default UserComponent

Теперь TypeScript заботится, чтобы мы случайно не передали в setUser что-то вроде объекта { value: 42 }. Если попробуем, компилятор обидится.

Работа с корневым состоянием (RootState)

В крупных приложениях у нас будет несколько срезов для разных частей состояния. Чтобы объединить их, используется объект RootState.

// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

export const store = configureStore({
  reducer: {
    user: userReducer, // Можно добавить больше редьюсеров
  },
});

// Тип для корневого состояния
export type RootState = ReturnType<typeof store.getState>;

// Тип для dispatch (с учетом асинхронных действий)
export type AppDispatch = typeof store.dispatch;

Теперь мы можем использовать RootState в хуке useSelector, чтобы точно знать, какие части состояния доступны.

Общая типизация состояния и действий

В Redux Toolkit работать с типизацией стало намного проще. Основные принципы:

  1. Всегда описывайте интерфейсы для состояния и полезной нагрузки действий.
  2. Используйте встроенные типы PayloadAction и ReturnType, чтобы избежать дублирования кода.
  3. Типизируйте RootState и AppDispatch для глобального использования.

В нашем примере всё вместе выглядит следующим образом:

  • Типизированный интерфейс состояния UserState.
  • Типизированные действия внутри среза.
  • Типизированное корневое состояние RootState.
  • Типизированные хуки useSelector, useDispatch.
2
Задача
Модуль 3: React, 23 уровень, 3 лекция
Недоступна
Типизация состояния и создание интерфейса
Типизация состояния и создание интерфейса
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ