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

Типизация данных запросов и ответов с TypeScript

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

Типизация данных запроса

Сегодня мы научимся типизировать запросы, ответы и научимся использовать Generics в React Query для работы с TypeScript.

Начнём с запроса к API. Допустим, у нас есть API, который возвращает список пользователей:

[
  {
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com"
  },
  {
    "id": 2,
    "name": "Jane Smith",
    "email": "jane.smith@example.com"
  }
]

Первое, что мы сделаем, — задекларируем интерфейс для этих данных. Это позволит нам гарантировать, что мы знаем структуру ответа.

interface User {
  id: number;
  name: string;
  email: string;
}

Теперь, когда у нас есть структура, мы можем использовать её в React Query. Представим, что мы получаем данные с помощью useQuery:

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

const fetchUsers = async (): Promise<User[]> => {
  const response = await axios.get('/api/users');
  return response.data;
};

const UsersList = () => {
  const { data, isLoading, error, isError } = useQuery<User[]м({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });

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

  if (isError && error instanceof Error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <ul>
      {data?.map((user) => (
        <li key={user.id}>
          {user.name} ({user.email})
        </li>
      ))}
    </ul>
  );
};

export default UsersList;

Разбираем код:

  1. Мы указали User[] как обобщённый тип (Generic) для useQuery. Это гарантирует, что data будет массивом объектов типа User.
  2. Функция fetchUsers возвращает Promise<User[]>, что явно показывает, какие данные мы ожидаем от API.
  3. Теперь TypeScript предупреждает нас, если мы неправильно работаем с данными, например, пытаемся получить свойство, которого нет в интерфейсе User.

Типизация данных, которые мы отправляем

Иногда нам нужно не только получать данные, но и отправлять их на сервер, например, при создании нового пользователя. Допустим, API принимает следующий JSON:

{
  "name": "Alice Johnson",
  "email": "alice.johnson@example.com"
}

Для этого мы создадим интерфейс:

interface CreateUserInput {
  name: string;
  email: string;
}

А также интерфейс для ответа сервера, если он возвращает созданного пользователя:

interface CreateUserResponse {
  id: number;
  name: string;
  email: string;
}

Теперь мы можем использовать эти интерфейсы при создании мутации с помощью useMutation:

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

const createUser = async (input: CreateUserInput): Promise<CreateUserResponse> => {
  const response = await axios.post('/api/users', input);
  return response.data;
};

const CreateUserForm = () => {
  const mutation = useMutation<CreateUserResponse, Error, CreateUserInput>({
    mutationFn: createUser,
  });

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const input: CreateUserInput = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
    };

    mutation.mutate(input);
  };

  if (mutation.isPending) {
    return <p>Saving...</p>;
  }

  if (mutation.isError) {
    return <p>Error: {mutation.error.message}</p>;
  }

  if (mutation.isSuccess) {
    return <p>User created: {mutation.data?.name}</p>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" placeholder="Email" type="email" required />
      <button type="submit">Create</button>
    </form>
  );
};

export default CreateUserForm; 

Что мы сделали:

  1. Типизировали входные данные запросов CreateUserInput и ожидаемый результат ответа CreateUserResponse.
  2. В компоненте CreateUserForm указали эти типы в useMutation при помощи Generics:
    • Первый тип CreateUserResponse — это результат мутации.
    • Второй тип Error — тип ошибки при выполнении запроса.
    • Третий тип CreateUserInput — тип данных, которые отправляются на сервер.

Типизация данных с неизвестной структурой

Иногда мы можем столкнуться с API, где структура данных не определена заранее. Например, если мы получаем универсальный ответ с различными данными в зависимости от запроса. Для таких случаев можно использовать TypeScript-синтаксис Record<string, any> или схему проверки типов.

Пример:

type ApiResponse = Record<string, any>;

// Пример использования:
const fetchDynamicData = async (): Promise<ApiResponse> => {
  const response = await axios.get('/api/dynamic');
  return response.data;
};

Здесь ApiResponse указывает, что мы получаем объект с произвольными ключами и значениями.

Особенности работы с типами в React Query

Иногда разработчики совершают ошибки при типизации данных. Например:

  1. Не указывают тип вообще, полагаясь на any. Это может привести к отсутствию проверки типов и проблемам в будущем.
  2. Забывают типизировать возможные состояния загрузки или ошибок. Например, не всегда обрабатывают error или isLoading, из-за чего пользовательский интерфейс может оказаться незавершённым.

Чтобы избежать этих проблем, старайтесь всегда использовать Generics для типизации useQuery, useMutation и других методов React Query.

Применение в реальных проектах

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

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