TypeScript
Для чего модуль
Использовать TypeScript как инженерный инструмент качества: уменьшать классы ошибок, ускорять рефакторинг и делать контракты системы прозрачными.
Результат после прохождения
- Вы строите доменные типы, а не «просто аннотации для компилятора».
- Вы уверенно применяете narrowing, discriminated unions и generics.
- Вы типизируете границы системы (UI/API/domain) с учетом runtime-валидации.
- Вы управляете строгими настройками TS в больших проектах без «type debt».
Термины и аббревиатуры
| Термин | Коротко |
|---|---|
Union | Тип из нескольких вариантов |
Narrowing | Уточнение типа проверками |
Generic | Тип-параметр |
DTO | Контракт данных на границе |
unknown | Безопасный неизвестный тип |
Фокус по грейдам
Junior: понимать базовые механики и объяснять их простыми примерами.Middle: применять тему в продуктовых сценариях с учетом рисков и ограничений.Senior: управлять архитектурными trade-offs, метриками и эволюцией решения.
Как работать с модулем
- Все примеры прогоняйте в
strict-режиме. - На каждый урок делайте мини-рефакторинг существующего кода.
- Для каждой типовой конструкции фиксируйте: какой класс ошибок она предотвращает и какой ценой.
Программа модуля
Урок 1. Базовая тип-модель
Цель: описывать доменную модель так, чтобы типы отражали реальные состояния системы.
Union, intersection, literal types
- Union описывает варианты состояния.
- Intersection собирает составные контракты.
- Literal types фиксируют допустимые значения.
Discriminated unions и exhaustiveness
type LoadState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function renderState(s: LoadState<string[]>) {
switch (s.status) {
case 'idle':
case 'loading':
case 'success':
case 'error':
return s.status;
default: {
const _exhaustive: never = s;
return _exhaustive;
}
}
}
Где ломается в проде
- Моделируют «частично известный объект» через
any. - Не описывают переходы состояния и получают impossible states.
- Выключают строгие проверки ради скорости.
Мини-задача (обязательная)
Смоделируйте состояние async-флоу через discriminated union, добавьте exhaustiveness check и покажите, какую ошибку это предотвращает.
Что спросит интервьюер:
как never помогает ловить ошибки в switch.
Критерий готовности по уроку: вы можете выразить типами все допустимые состояния фичи и исключить невозможные комбинации.
Урок 2. Generics и переиспользование
Цель: писать переиспользуемые типобезопасные API без перегрузки сложностью.
Где generics действительно нужны
- Обобщенные контейнеры/репозитории.
- Типобезопасные utility функции.
- Переиспользуемые UI-абстракции (таблицы, формы, query hooks).
Ограничения и вывод типов
- Используйте
extendsдля ограничений. - Не прячьте доменную семантику за слишком абстрактными
T/U/V. - Проверяйте, что infer работает как ожидается в consumer-коде.
function byId<T extends { id: string }>(items: T[]): Record<string, T> {
return Object.fromEntries(items.map((i) => [i.id, i]));
}
Utility types в реальной жизни
Pick/Omit/Partial/Required/Readonly/Record полезны,
когда отражают контракт, а не используются как «быстрый костыль».
Где ломается в проде
- Generic API становится сложнее, чем конкретная реализация.
- Типы «протекают» через
as unknown as. - Повсеместный
Partial<T>размывает инварианты.
Мини-задача (обязательная)
Реализуйте generic-тип и функцию пагинации: данные + мета + безопасный контракт ошибок.
Что спросит интервьюер: когда generic делает API слишком сложным и как упростить.
Критерий готовности по уроку: вы пишете generic-решения, которые повышают надежность и остаются читаемыми для команды.
Урок 3. Типизация границ системы
Цель: сделать границы UI/API/domain устойчивыми к изменениям и ошибкам интеграции.
Compile-time vs runtime
TypeScript не валидирует входные данные в runtime. Если API вернул неожиданный payload, compile-time не спасет.
DTO и валидация входа
- Определите входной DTO.
- Проверьте payload на runtime boundary.
- Только после этого маппите в внутренние доменные типы.
// идея: сначала parse/validate, потом используем тип как trusted
Типизация ошибок и событий
- Ошибки должны иметь машинно-читаемый
code. - UI состояния и domain events описываются типами отдельно.
- Переходы между состояниями — явные и проверяемые.
Где ломается в проде
- «Доверяют» внешнему API без runtime-check.
- Смешивают transport и domain типы.
- Type-safe facade внутри, но
anyна внешней границе.
Мини-задача (обязательная)
Опишите типобезопасный flow: API response -> runtime validation -> domain mapping -> UI state. Добавьте один пример неверного payload и ожидаемое поведение.
Что спросит интервьюер: почему compile-time типизация не заменяет runtime валидацию.
Критерий готовности по уроку: вы можете объяснить и показать устойчивую типизацию границ, которая переживает изменения внешнего контракта.
Урок 4. TS в большом проекте
Цель: управлять TypeScript как частью инженерного процесса команды.
Конфигурация и strictness
strict,noUncheckedIndexedAccess,exactOptionalPropertyTypes(по контексту проекта).- Запрет «тихих» путей деградации (
any, неявные assertions). - Стратегия миграции legacy-кода без freeze разработки.
Типовой техдолг
- Локальные
anyразмножаются и размывают контракты. ts-ignoreостается без срока удаления.- Нет ownership за типовые правила и code review стандарты.
Процесс качества
- Type coverage/линты как quality gate.
- Checklist в PR: какие типовые риски добавлены/сняты.
- Документация типовых паттернов команды.
Где ломается в проде
- Обновление библиотеки рушит типовую совместимость.
- Рефакторинг проходит компиляцию, но ломает runtime boundary.
- Нет механики отслеживания type debt.
Мини-задача (обязательная)
Сделайте план миграции legacy-модуля к strict TS на 3 итерации: quick wins, среднесрочные фиксы, долгосрочные стандарты.
Что спросит интервьюер: как внедрять строгий TS в проект без остановки delivery.
Критерий готовности по уроку: вы можете управлять TS-качеством проекта как системной инженерной задачей.
Практика
Работайте через 4 проверяемых блока. Каждый блок должен закончиться артефактом, который показывает, какой класс ошибок TypeScript реально снижает.
| Блок | Ответить | Practice action | Readiness |
|---|---|---|---|
| State modeling | Middle q-13, q-15 | Async UI state model: idle/loading/success/empty/error, discriminated union, never exhaustiveness, render mapping | Вы показываете impossible state, который типы запрещают, и объясняете, где всё равно нужен runtime guard |
| Generics and utility types | Middle q-14, q-16 | Generic pagination + typed error contract: Page<T>, ApiError, selectById<T extends { id: string }>(), utility migration table | Вы объясняете, где generic сохраняет связь input/output, где абстракция вредна, и почему fetchJson<T>() не валидирует payload |
| Boundary validation | Middle q-13..q-16, Senior q-18 | TypeScript boundary pack: ApiDto -> runtime validation -> DomainModel -> ViewState, invalid payload example, fallback/error behavior | Вы отделяете transport DTO от domain model и показываете runtime check до доверенного типа |
| Strict TS at scale | Senior q-16, Senior q-20 | Strict migration plan для legacy-модуля: quick wins, any/ts-ignore inventory, quality gates, review checklist, owner and deadline | Вы управляете type debt как engineering process: gate, owner, exception policy и removal plan |
Если блок получает partial или not ready, не переходите дальше: перепишите артефакт до состояния type intent -> runtime boundary -> failure mode -> verification.
Связь с треками и вопросами
- Треки: Middle трек, Senior трек.
- Вопросы: Middle q-13..q-16, Senior q-16, Senior q-18, Senior q-20.
- Route practice: Middle день 4, Middle день 8, Senior день 10, TypeScript boundary pack.
- Повторение через 24 часа: один вопрос про
type/interface, один про generics, один про discriminated union, один про DTO/runtime boundary.
Критерий готовности
Ready, если вы используете TS не как «украшение», а как механизм снижения ошибок и ускорения безопасных изменений:
- state model запрещает impossible states и имеет exhaustiveness check;
- generic API сохраняет типовую связь без
as unknown asи лишней абстракции; - API boundary валидирует runtime payload до domain mapping;
- utility types отражают контракт, а не скрывают неясную модель данных;
- strict migration plan имеет owner, gates, exception policy и deadline для type debt.
Partial, если типы описаны, но нет invalid payload, runtime check или quality gate.
Not ready, если ответ сводится к "добавить типы", any, as или fetchJson<T>() без проверки фактического payload.
Артефакты после модуля
- Набор типовых паттернов (state, errors, events, DTO mapping).
- Шаблон generic pagination + error model.
- Boundary mapping table:
ApiDto -> DomainModel -> ViewState. - Invalid payload test cases and fallback behavior.
- Документ миграции legacy к strict TS.
- Набор из 6 сильных interview-ответов по TypeScript.