Продвинутый TypeScript: создаём полностью типобезопасный API-клиент с помощью дженериков, условных и отображаемых типов
Представьте знакомую сцену. Ваша команда выпускает новый эндпоинт в пятницу днём. В понедельник фронтенд всё ещё передаёт старое поле userId вместо user_id, форма ошибки изменилась с { message } на { error: { code } }, и ничего не ломается, пока клиент не сообщит о пустом экране. Компилятор всё это время молчал, потому что API-клиент был типизирован как any. Именно этот пробел призван закрыть курс Продвинутый TypeScript на asibiont.com.
Продвинутый TypeScript — это не заучивание синтаксиса. Это обучение компилятора пониманию вашего домена: маршрутов, HTTP-глаголов, ветвей ошибок и полезных нагрузок ответов. В этой статье мы шаг за шагом построим готовый к продакшену, полностью типобезопасный API-клиент — обобщённое отображение ответов, условные типы для HTTP-методов, шаблонные литеральные типы для параметров маршрутов и размеченные объединения для обработки ошибок. Каждая из описанных ниже техник подробно разбирается в курсе Продвинутый TypeScript, где нейронная сеть генерирует персонализированные уроки, адаптирующиеся к вашему текущему уровню.
Проблема: когда any стоит реальных денег
Большинство команд начинают с такого помощника:
async function request(url: string, options?: RequestInit): Promise<any> {
const res = await fetch(url, options);
return res.json();
}
Это работает — до тех пор, пока не перестаёт. Возвращаемый тип any отключает автодополнение, скрывает опечатки и молча принимает неправильные имена полей. Согласно документации TypeScript по any (typescriptlang.org/docs/handbook/2/everyday-types.html), использование any полностью отказывается от проверки типов, поэтому в больших кодовых базах постепенно накапливаются ошибки времени выполнения, которые типы должны были бы поймать.
Решение — не «больше типов». Решение — более умные типы. Именно это и дают продвинутые возможности TypeScript.
Шаг 1: Обобщённое отображение ответов
Дженерики позволяют функции сохранять связь между входом и выходом. Вместо возврата any мы описываем форму ответа как параметр типа:
type ApiResponse<T> = { data: T; status: number };
async function get<T>(path: string): Promise<ApiResponse<T>> {
const res = await fetch(path);
return { data: await res.json(), status: res.status };
}
const user = await get<{ id: number; name: string }>('/users/1');
// user.data.name полностью типизирован
Как только клиент становится обобщённым, каждый вызывающий получает автодополнение и проверки на этапе компиляции бесплатно. Этот паттерн — основа таких библиотек, как TanStack Query и типизированных обёрток над fetch, и это одна из первых тем, которые отрабатываются в курсе Продвинутый TypeScript на практических задачах.
Шаг 2: Условные типы для HTTP-методов
Не каждый HTTP-метод возвращает тело. DELETE часто возвращает 204 No Content, а GET — JSON. Условные типы позволяют выразить это правило прямо в системе типов:
type Method = 'GET'
| 'POST' | 'PUT' | 'DELETE';
type Result<M extends Method> =
M extends 'DELETE' ? void : { data: unknown };
async function call<M extends Method>(m: M, url: string): Promise<Result<M>> {
// реализация
return undefined as Result<M>;
}
Теперь компилятор знает, что call('DELETE', url) разрешается в void, а call('GET', url) — в объект. Это устраняет целые классы ошибок «cannot read property of undefined» на уровне типов. Справочник MDN по методам HTTP-запросов (developer.mozilla.org/en-US/docs/Web/HTTP/Methods) подтверждает семантику, которую кодирует эта типизация.
Шаг 3: Шаблонные литеральные типы для параметров маршрутов
Маршруты вроде /users/:id/posts/:postId — это строки, но они несут структуру. Шаблонные литеральные типы позволяют TypeScript извлекать эту структуру:
type ExtractParams<T extends string> =
T extends `${string}:${infer P}/${infer Rest}`
? P | ExtractParams<`/${Rest}`>
: T extends `${string}:${infer P}`
? P
: never;
type Params = ExtractParams<'/users/:id/posts/:postId'>;
// 'id' | 'postId'
С этим на месте функция может требовать объект, ключи которого соответствуют параметрам маршрута — ошибитесь в postId, и компилятор откажется собирать. Это тот же механизм, который лежит в основе типизированных роутеров в таких фреймворках, как tRPC, и типизированных REST-клиентов.
Шаг 4: Размеченные объединения для ошибок
Ошибки — это место, где типобезопасность обычно умирает. Размеченное объединение даёт каждой ветви ошибки литеральное поле kind, так что операторы switch становятся исчерпывающими:
type ApiError =
| { kind: 'network'; retryable: true }
| { kind: 'auth'; status: 401 }
| { kind: 'validation'; fields: string[] };
function handle(err: ApiError) {
switch (err.kind) {
case 'network': return retry();
case 'auth': return redirectToLogin();
case 'validation': return showFields(err.fields);
}
}
Если вы позже добавите новый вид ошибки, TypeScript отметит каждый switch, который больше не покрывает все случаи. Этот паттерн описан в разделе руководства TypeScript о сужении типов (typescriptlang.org/docs/handbook/2/narrowing.html).
Почему обучение с ИИ подходит для продвинутого TypeScript
Продвинутый TypeScript необычен тем, что прогресс не линеен. Один разработчик борется с условными типами, но находит отображаемые типы интуитивно понятными; другому нужно освежить дженерики, прежде чем шаблонные литералы обретут смысл. Фиксированная программа заставляет всех проходить одну и ту же последовательность.
На asibiont.com нейронная сеть генерирует персонализированные уроки для каждого студента. Она читает ваш текущий уровень, ваши цели и ошибки, которые вы допускаете в практических задачах, а затем строит следующий урок вокруг них. Сложные темы — например, рекурсивный условный тип из шага 3 — сначала объясняются более простым языком, с постепенно усложняющимися примерами. Уроки текстовые и доступны круглосуточно, так что вы можете учиться в 6 утра перед работой или в полночь после деплоя. Нет видеолекций, которые нужно перематывать; вы читаете, пишете код, получаете обратную связь.
Это важно, потому что продвинутый TypeScript — навык, который строится написанием типов, а не наблюдением за тем, как их пишет кто-то другой. ИИ-наставник адаптирует сложность в реальном времени, поэтому многие студенты отмечают, что концепции, казавшиеся непроницаемыми в документации, наконец становятся понятными.
Для кого этот курс
| Профиль | Почему подходит |
|---|---|
| Фронтенд/бэкенд-разработчики среднего уровня | Выйти за пределы any и начать типизировать реальные API |
| Full-stack инженеры | Безопасно делиться типами между клиентом и сервером |
| Авторы библиотек | Писать файлы деклараций и публичные дженерики, которые масштабируются |
| Техлиды | Сократить ошибки времени выполнения в большой кодовой базе |
Если вы уже знаете основы TypeScript — интерфейсы, объединения, простые дженерики — вы готовы. Если вы пишете any чаще раза в неделю, этот курс изменит то, как вы работаете.
Чему вы действительно научитесь
В течение курса студенты работают с дженериками, условными типами, отображаемыми типами, шаблонными литеральными типами и размеченными объединениями. Они практикуют паттерны функционального программирования в TypeScript, строят типобезопасные билдеры, пишут файлы деклараций для внешних библиотек и оптимизируют продакшен-сборки. Каждая тема сопровождается практическими задачами, потому что читать про infer — не то же самое, что использовать его.
Начните строить сегодня
Лучший способ понять продвинутый TypeScript — построить с его помощью что-то реальное. API-клиент, который мы набросали здесь — обобщённые ответы, условные типы с учётом методов, шаблонные литералы для параметров маршрутов и размеченные объединения ошибок — это именно тот проект, который вы завершите на asibiont.com. Уроки адаптируются к вам, а не наоборот.
Готовы перестать угадывать и начать типизировать? Запишитесь на Продвинутый TypeScript на asibiont.com и позвольте ИИ построить учебный путь вокруг вашего кода.
Комментарии