JavaRush /Курсы /Модуль 3: React /Типизация мутаций с TypeScript и обработка ошибок

Типизация мутаций с TypeScript и обработка ошибок

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

Типизация данных мутации с TypeScript

Мутации без типизации — это как отправлять посылку без наклейки с адресом. Вы никогда не знаете, куда она придёт или что пойдёт не так. TypeScript поможет нам минимизировать ошибки.

Интерфейсы для типизации мутаций

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

interface Task {
  id: number;
  title: string;
  description: string;
}

interface CreateTaskInput {
  title: string;
  description: string;
}

Хук useMutation с Generics

Теперь, используя TypeScript, мы можем типизировать параметры и возвращаемые данные мутации:

import { useMutation } from '@tanstack/react-query';
import axios from 'axios';

const createTask = async (task: CreateTaskInput): Promise<Task> => {
  const response = await axios.post<Task>('/api/tasks', task);
  return response.data;
};

const useCreateTaskMutation = () => {
  return useMutation<Task, Error, CreateTaskInput>({
    mutationFn: createTask,
  });
};

Обратите внимание на Generics, которые принимает useMutation:

  1. Task — тип данных, которые возвращает сервер (успешный результат).
  2. Error — тип возможной ошибки.
  3. CreateTaskInput — тип данных, передаваемых в мутацию.

Теперь наша мутация полностью типизирована, и TypeScript будет помогать нам на каждом шагу.

Обработка состояний мутации

Хук useMutation предоставляет удобные состояния "жизни" мутации:

  • isPending — мутация выполняется.
  • isSuccess — мутация успешно завершена.
  • isError — произошла ошибка.
  • error — информация об ошибке.

Давайте реализуем пример добавления новой задачи с обработкой состояний.

Пример компонента

import React, { useState } from 'react';
import { useCreateTaskMutation } from './mutations'; // Помните пример выше?

const AddTaskForm: React.FC = () => {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  const {
    mutate,        // Функция для вызова мутации
    isPending,     // Состояние загрузки
    isError,       // Состояние ошибки
    error,         // Детали ошибки
    isSuccess,     // Успешное выполнение
  } = useCreateTaskMutation();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    mutate(
      { title, description },
      {
        onSuccess: () => {
          // Очистка формы после успешного создания
          setTitle('');
          setDescription('');
        },
      }
    );
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Task title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />
        <textarea
          placeholder="Task description"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        />
        <button type="submit" disabled={isPending}>
          {isPending ? 'Adding...' : 'Add Task'}
        </button>
      </form>

      {isError && <p style={{ color: 'red' }}>Error: {error?.message}</p>}
      {isSuccess && <p style={{ color: 'green' }}>Task added successfully!</p>}
    </div>
  );
};

export default AddTaskForm; 

Обратите внимание, как мы используем состояния isLoading, isError и isSuccess для улучшения UX (пользовательского опыта).

Обработка ошибок в мутациях

Ошибки — это неизбежная часть работы с сервером. React Query предоставляет несколько удобных способов обработки ошибок.

onError и возвращение кэшированных данных

Мы можем использовать опцию onError для выполнения действий при ошибке. Например, мы можем уведомить пользователя о проблеме или даже вернуть кэшированные данные (если они есть):

const { mutate } = useCreateTaskMutation({
  onError: (error: Error) => {
    alert(`Something went wrong: ${error.message}`);
  },
});

Отмена изменений в случае ошибки

Для более сложных случаев можно использовать оптимистические обновления. Например, в случае ошибки изменения откатываются:

import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

const deleteTask = async (taskId: number): Promise<void> => {
  await axios.delete(`/api/tasks/${taskId}`);
};

export const useDeleteTaskMutation = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: deleteTask,

    // Оптимистичное обновление при удалении задачи
    onMutate: async (taskId: number) => {
      await queryClient.cancelQueries({ queryKey: ['tasks'] });

      const previousTasks = queryClient.getQueryData<Task[]>(['tasks']);

      queryClient.setQueryData<Task[]>(['tasks'], (oldTasks) =>
        oldTasks?.filter((task) => task.id !== taskId)
      );

      return { previousTasks };
    },

    // Откат в случае ошибки
    onError: (_error, _taskId, context) => {
      if (context?.previousTasks) {
        queryClient.setQueryData(['tasks'], context.previousTasks);
      }
    },

    // Повторный запрос задач после завершения мутации
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['tasks'] });
    },
  });
};

Здесь:

  • onMutate использует оптимистическое удаление задачи из кэша.
  • onError восстанавливает предыдущие данные в случае ошибки.

Обработка сложных мутаций

Если необходимо отправить сложные данные (например, вложенные JSON-объекты), не забывайте о типизации. Используйте интерфейсы и обобщения TypeScript, чтобы избежать несоответствий:

import { useMutation } from '@tanstack/react-query';
import axios from 'axios';

interface NestedTaskInput {
  title: string;
  description: string;
  subtasks: {
    title: string;
    completed: boolean;
  }[];
}

const createNestedTask = async (task: NestedTaskInput): Promise<Task> => {
  const response = await axios.post<Task>('/api/nested-tasks', task);
  return response.data;
};

export const useCreateNestedTaskMutation = () => {
  return useMutation<Task, Error, NestedTaskInput>({
    mutationFn: createNestedTask,
  });
};
1
Задача
Модуль 3: React, 8 уровень, 6 лекция
Недоступна
Создание типизированной мутации для добавления пользователя
Создание типизированной мутации для добавления пользователя
1
Задача
Модуль 3: React, 8 уровень, 6 лекция
Недоступна
Добавление задачи с обработкой ошибок
Добавление задачи с обработкой ошибок
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Антон Уровень 90
25 июля 2025
isLoading yне работает в идее. isPending проходит
Антон Уровень 90
25 июля 2025
isLoading это скорее всего опечатка. Правильно isPending