Расскажите о случае, когда особенности TypeScript повлияли на архитектурное решение, производительность или сопровождение проекта.
TypeScript выявил скрытые архитектурные проблемы: непоследовательные формы API-ответов, избыточные ре-рендеры и отсутствие рантаймовой валидации, что привело к единой Zod-схеме как источнику истины.
Влияние TypeScript на архитектурные решения
Один из наиболее показательных случаев — рефакторинг слоя API-клиентов в крупном SPA при переходе с JavaScript на TypeScript. Типизация выявила скрытые архитектурные проблемы и повлияла на итоговое решение.
Контекст
Проект: React-приложение с 80+ API-эндпоинтами, каждый вызов через axios без типов. Симптомы: частые runtime ошибки вида Cannot read property 'x' of undefined, нет автодополнения в IDE.
Архитектурное решение: code generation vs Zod
Первый вариант — сгенерировать типы из OpenAPI через openapi-typescript:
npx openapi-typescript https://api.example.com/openapi.json -o src/types/api.d.ts
Это дало 500+ строк типов, но без рантаймовой валидации. Второй вариант — Zod-схемы как единственный источник истины:
import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum(['admin', 'user', 'viewer']),
createdAt: z.string().datetime(),
});
type User = z.infer<typeof UserSchema>; // тип = схема, нет дублирования
async function fetchUser(id: string): Promise<User> {
const data = await api.get(`/users/${id}`);
return UserSchema.parse(data); // throws при несоответствии
}
Влияние на сопровождение
TypeScript выявил, что 12 из 80 эндпоинтов возвращали разные формы данных в зависимости от условий бэкенда. Это потребовало явного моделирования union-типов:
type ApiResponse<T> =
| { status: 'ok'; data: T }
| { status: 'error'; code: string; message: string };
async function safeGet<T>(schema: z.ZodType<T>, url: string): Promise<ApiResponse<T>> {
try {
const raw = await api.get(url);
return { status: 'ok', data: schema.parse(raw) };
} catch (e) {
return { status: 'error', code: 'PARSE_ERROR', message: String(e) };
}
}
Влияние на производительность
Переход на TypeScript обнаружил избыточные re-render из-за неправильной мемоизации: без типов props передавались как any, TypeScript показал, что некоторые объекты создавались inline при каждом рендере.
Подводные камни
- Начинать миграцию с
allowJs: trueиcheckJs: falseудобно, но создаёт двухскоростную кодовую базу — установите дедлайн на удаление JS-файлов. - Zod.parse бросает исключение синхронно — оборачивайте в try/catch или используйте
safeParseдля предсказуемого контроля потока. - Кодогенерация из OpenAPI отстаёт от бэкенда при ручных изменениях схемы — автоматизируйте через pre-commit hook или CI шаг.
- Строгая типизация иногда приводит к over-engineering: не все данные нужно парсить через Zod, внутренние вычисления можно оставить на уровне TypeScript типов.
- Generic-тяжёлые утилиты замедляют language server — профилируйте через
--extendedDiagnostics. - Рефакторинг типов в публичном API пакета монорепо ломает все потребители сразу — версионируйте внутренние пакеты или используйте changesets.
- TypeScript не защищает от проблем с мутабельностью — используйте
Readonlyиas constтам, где важна иммутабельность. - Миграция большой кодовой базы с JS должна идти модульно — не пытайтесь включить strict одним PR.
What hurts your answer
- Выдумывать опыт или говорить слишком общими фразами
- Не объяснять свою личную роль в работе с TypeScript
- Не показывать результат, метрики или извлечённые уроки
What they're listening for
- Может подготовить честный пример использования TypeScript
- Показывает свою роль, решения и результат
- Умеет рефлексировать над trade-offs и уроками