Интерфейсы для типизации данных
Когда мы работаем с API, у нас есть два основных типа данных:
- Данные, которые отправляются.
- Данные, которые возвращаются.
TypeScript позволяет создавать интерфейсы для описания структуры этих данных.
Пример: интерфейсы для данных
Предположим, у нас есть REST API, который возвращает следующую структуру данных для пользователей при запросе GET /users:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"isAdmin": false
}
Мы можем описать эту структуру с помощью интерфейса:
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
Для списка пользователей (возврат массива объектов) создадим тип:
type UsersResponse = User[];
Теперь TypeScript будет контролировать, чтобы любая операция с данными соответствовала этим типам.
Типизация запросов с fetch
Теперь вспомним базовый запрос с fetch и добавим типизацию.
Пример 1: типизация для GET-запроса
Напишем функцию, которая получает список пользователей:
Код без типизации:
async function fetchUsers() {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return data;
}
С типизацией:
async function fetchUsers(): Promise<UsersResponse> {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data: UsersResponse = await response.json();
return data;
}
Что здесь поменялось:
- Мы добавили возвращаемый тип
Promise<UsersResponse>— функция возвращает промис, который в будущем разрешится массивом пользователей. - Тип пользователей
UsersResponseприменяется при парсинге JSON (const data: UsersResponse = ...).
Теперь, если данные на сервере изменятся (например, будет добавлено поле или изменён тип), TypeScript сразу сообщит об этом.
Пример 2: типизация для POST-запроса
Предположим, нам нужно добавить нового пользователя через API, отправив его данные с запросом POST.
interface NewUser {
name: string;
email: string;
password: string;
}
Функция для отправки POST-запроса:
async function createUser(user: NewUser): Promise<User> {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const createdUser: User = await response.json();
return createdUser;
}
Что мы сделали:
- Создали отдельный интерфейс
NewUserдля данных, отправляемых в запросе. - Указали возвращаемый тип как
Promise<User>для данных, которые мы получим от сервера. - Явно типизировали тело ответа сервера (
const createdUser: User).
Теперь вызовы createUser требуют точного соответствия структур данных, что предотвращает ошибки.
Улучшение читаемости и повторного использования
Для более читаемого и повторного использования кода можно написать универсальную функцию запроса, которая принимает URL, метод и данные.
async function apiRequest<TRequest, TResponse>(
url: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
body?: TRequest
): Promise<TResponse> {
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data: TResponse = await response.json();
return data;
}
Использование:
const newUser: NewUser = { name: 'Bob', email: 'bob@example.com', password: '123456' };
const createdUser = await apiRequest<NewUser, User>(
'https://api.example.com/users',
'POST',
newUser
);
console.log(createdUser.name);
Такой подход делает функцию универсальной — она может работать с любым запросом при условии указания типов.
Тип Response в TypeScript
JavaScript имеет встроенный объект Response для работы с HTTP-ответами. TypeScript предоставляет для него тип, который можно использовать.
Пример проверки статуса ответа:
async function fetchData(url: string): Promise<string> {
const response: Response = await fetch(url);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Error: ${response.statusText} - ${errorText}`);
}
return response.text();
}
Типичные ошибки и как их избежать
Работая с типизацией запросов, важно помнить несколько моментов:
Несоответствие типов. Если сервер возвращает данные, которые не соответствуют вашим интерфейсам, при отсутствии строгой проверки может возникнуть баг. Например, сервер добавил новое обязательное поле, или изменил тип данных — TypeScript здесь ваш главный защитник.
Нет обработки ошибок. Не пропускайте проверку статуса ответа
response.ok. Запрос может вернуть 404, 500, или другой HTTP-код, даже если он не вызывает явную ошибку вfetch.Неверная типизация тела запроса. Убедитесь, что данные, отправляемые в
body, соответствуют интерфейсу. В противном случае сервер просто может отклонить запрос.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ