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

Типизация useReducer — интерфейсы для состояния и действий

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

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

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

Зачем вообще типизировать состояние?

Представьте себе гостиницу, где нет списка гостей. Администратор не знает, кто заселился и кто уехал. Примерно так же работает приложение без типизации: мы не знаем, какие данные наш редьюсер вообще поддерживает.

Типизация состояния state помогает:

  • определить структуру данных, которые редьюсер будет обрабатывать;
  • предотвратить ошибки, когда случайно пытаетесь обратиться к несуществующему полю;
  • облегчить процесс написания функций, работающих с состоянием.

Пример типизации простого состояния

Начнем с небольшого примера. Допустим, мы разрабатываем Todo-приложение, где есть список задач и флаг загрузки данных. Вот как можно описать состояние с помощью TypeScript:

// Интерфейс описывает состояние
interface TodoState {
  todos: { id: number; text: string; completed: boolean }[];
  isLoading: boolean;
}

// Пример начального состояния
const initialState: TodoState = {
  todos: [],
  isLoading: false,
};

Здесь TodoState — это интерфейс, который описывает данные нашего состояния:

  1. todos — это массив объектов, где каждый объект представляет задачу.
  2. isLoading — булево значение, отвечающее за отображение состояния загрузки.

Применяя такой интерфейс, вы всегда уверены, что состояние точно соответствует ожидаемой структуре.

Типизация действий action

Что такое "action" в контексте useReducer?

Action — это то, что инициирует любые изменения в состоянии. Это объект, где мы указываем:

  • тип действия (обычно строка, например: "ADD_TODO" или "TOGGLE_TODO");
  • дополнительные данные (например, текст новой задачи или идентификатор задачи).

Каждое действие сообщает редьюсеру: "Привет, что-то изменилось! Вот подробности."

Как типизировать действия?

Для начала определим типы допустимых действий. Например, в нашем Todo-приложении:

  1. Добавление новой задачи.
  2. Переключение состояния задачи (выполнено или не выполнено).
  3. Загрузка задач.

Мы можем создать тип для действий несколькими способами. Один из самых удобных подходов — это использование TypeScript union types (объединений).

// Определяем типы действий
type TodoAction =
  | { type: 'ADD_TODO'; payload: { text: string } }  // Для добавления новой задачи
  | { type: 'TOGGLE_TODO'; payload: { id: number } } // Для переключения выполнения задачи
  | { type: 'SET_LOADING'; payload: { isLoading: boolean } }; // Установка флага загрузки

Давайте разберем пример:

  1. type — это обязательное поле, которое указывает на тип действия.
  2. payload — это объект с дополнительной информацией, необходимой для выполнения действия, и он тоже типизирован.

Пример использования типизированных действий в редьюсере

Когда у нас есть типизированные действия, их легко использовать в редьюсере. Например:

// Редьюсер с типизировным состоянием и действиями
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
  switch (action.type) {
    case 'ADD_TODO':
      // Добавляем новую задачу в массив
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: Date.now(), text: action.payload.text, completed: false },
        ],
      };

    case 'TOGGLE_TODO':
      // Ищем задачу по id и переключаем её completed
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };

    case 'SET_LOADING':
      // Изменяем флаг загрузки
      return {
        ...state,
        isLoading: action.payload.isLoading,
      };

    default:
      return state;
  }
};

Обратите внимание, как TypeScript помогает:

  • Подсвечивает ошибки, если вы используете неправильный тип действия.
  • Уверяет, что payload передается с правильной структурой.

А что если действие не передает payload?

Иногда действиям не требуется никаких дополнительных данных. Например, сброс состояния к начальному. В таких случаях достаточно указать только тип действия:

type ClearAction = { type: 'CLEAR_STATE' };

Пример: объединяем типизацию состояния и действий

Теперь давайте создадим небольшой пример с использованием useReducer. Мы создадим компонент TodoList, где можно добавлять и переключать задачи.

import React, { useReducer } from 'react';

// Типизация состояния
interface TodoState {
  todos: { id: number; text: string; completed: boolean }[];
  isLoading: boolean;
}

// Типизация действий
type TodoAction =
  | { type: 'ADD_TODO'; payload: { text: string } }
  | { type: 'TOGGLE_TODO'; payload: { id: number } };

// Начальное состояние
const initialState: TodoState = {
  todos: [],
  isLoading: false,
};

// Редьюсер
const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: Date.now(), text: action.payload.text, completed: false },
        ],
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };

    default:
      return state;
  }
};

// Компонент с TodoList
const TodoList: React.FC = () => {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  const addTodo = () => {
    const text = prompt('Введите текст задачи:');
    if (text) {
      dispatch({ type: 'ADD_TODO', payload: { text } });
    }
  };

  const toggleTodo = (id: number) => {
    dispatch({ type: 'TOGGLE_TODO', payload: { id } });
  };

  return (
    <div>
      <h2>Список задач</h2>
      <button onClick={addTodo}>Добавить задачу</button>
      <ul>
        {state.todos.map(todo => (
          <li
                  key={todo.id}
                  style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
            onClick={() => toggleTodo(todo.id)}
          >
            {todo.text}
        </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

Типичные ошибки и пути их решения

Одна из часто встречающихся ошибок — отсутствие типизации для action.type и action.payload. Например, если случайно передать неверный тип действия, TypeScript не предупредит вас, если типы не были описаны. Поэтому всегда создавайте строгую типизацию для всех возможных действий.

Еще одна проблема — забыть типизировать state. Если вы не зададите интерфейс состояния, то, скорее всего, столкнетесь с ошибками, обращаясь к полям, которых не существует.

Наконец, избегайте огромных редьюсеров с сотнями условий case. Это не только усложняет код, но и делает его практически непроверяемым.

Ну что, теперь вы вооружены более глубокими знаниями о типизации в useReducer!

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