Перейти к основному содержимому

TypeScript

Для чего модуль​

Использовать TypeScript как инженерный инструмент качества: уменьшать классы ошибок, ускорять рефакторинг и делать контракты системы прозрачными.

Результат после прохождения​

  1. Вы строите доменные типы, а не «просто аннотации для компилятора».
  2. Вы уверенно применяете narrowing, discriminated unions и generics.
  3. Вы типизируете границы системы (UI/API/domain) с учетом runtime-валидации.
  4. Вы управляете строгими настройками TS в больших проектах без «type debt».

Термины и аббревиатуры​

ТерминКоротко
UnionТип из нескольких вариантов
NarrowingУточнение типа проверками
GenericТип-параметр
DTOКонтракт данных на границе
unknownБезопасный неизвестный тип

Фокус по грейдам​

  1. Junior: понимать базовые механики и объяснять их простыми примерами.
  2. Middle: применять тему в продуктовых сценариях с учетом рисков и ограничений.
  3. Senior: управлять архитектурными trade-offs, метриками и эволюцией решения.

Как работать с модулем​

  1. Все примеры прогоняйте в strict-режиме.
  2. На каждый урок делайте мини-рефакторинг существующего кода.
  3. Для каждой типовой конструкции фиксируйте: какой класс ошибок она предотвращает и какой ценой.

Программа модуля​

Урок 1. Базовая тип-модель​

Цель: описывать доменную модель так, чтобы типы отражали реальные состояния системы.

Union, intersection, literal types​

  1. Union описывает варианты состояния.
  2. Intersection собирает составные контракты.
  3. 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;
}
}
}

Где ломается в проде​

  1. Моделируют «частично известный объект» через any.
  2. Не описывают переходы состояния и получают impossible states.
  3. Выключают строгие проверки ради скорости.

Мини-задача (обязательная)​

Смоделируйте состояние async-флоу через discriminated union, добавьте exhaustiveness check и покажите, какую ошибку это предотвращает.

Что спросит интервьюер: как never помогает ловить ошибки в switch.

Критерий готовности по уроку: вы можете выразить типами все допустимые состояния фичи и исключить невозможные комбинации.

Урок 2. Generics и переиспользование​

Цель: писать переиспользуемые типобезопасные API без перегрузки сложностью.

Где generics действительно нужны​

  1. Обобщенные контейнеры/репозитории.
  2. Типобезопасные utility функции.
  3. Переиспользуемые UI-абстракции (таблицы, формы, query hooks).

Ограничения и вывод типов​

  1. Используйте extends для ограничений.
  2. Не прячьте доменную семантику за слишком абстрактными T/U/V.
  3. Проверяйте, что 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 полезны, когда отражают контракт, а не используются как «быстрый костыль».

Где ломается в проде​

  1. Generic API становится сложнее, чем конкретная реализация.
  2. Типы «протекают» через as unknown as.
  3. Повсеместный Partial<T> размывает инварианты.

Мини-задача (обязательная)​

Реализуйте generic-тип и функцию пагинации: данные + мета + безопасный контракт ошибок.

Что спросит интервьюер: когда generic делает API слишком сложным и как упростить.

Критерий готовности по уроку: вы пишете generic-решения, которые повышают надежность и остаются читаемыми для команды.

Урок 3. Типизация границ системы​

Цель: сделать границы UI/API/domain устойчивыми к изменениям и ошибкам интеграции.

Compile-time vs runtime​

TypeScript не валидирует входные данные в runtime. Если API вернул неожиданный payload, compile-time не спасет.

DTO и валидация входа​

  1. Определите входной DTO.
  2. Проверьте payload на runtime boundary.
  3. Только после этого маппите в внутренние доменные типы.
// идея: сначала parse/validate, потом используем тип как trusted

Типизация ошибок и событий​

  1. Ошибки должны иметь машинно-читаемый code.
  2. UI состояния и domain events описываются типами отдельно.
  3. Переходы между состояниями — явные и проверяемые.

Где ломается в проде​

  1. «Доверяют» внешнему API без runtime-check.
  2. Смешивают transport и domain типы.
  3. Type-safe facade внутри, но any на внешней границе.

Мини-задача (обязательная)​

Опишите типобезопасный flow: API response -> runtime validation -> domain mapping -> UI state. Добавьте один пример неверного payload и ожидаемое поведение.

Что спросит интервьюер: почему compile-time типизация не заменяет runtime валидацию.

Критерий готовности по уроку: вы можете объяснить и показать устойчивую типизацию границ, которая переживает изменения внешнего контракта.

Урок 4. TS в большом проекте​

Цель: управлять TypeScript как частью инженерного процесса команды.

Конфигурация и strictness​

  1. strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes (по контексту проекта).
  2. Запрет «тихих» путей деградации (any, неявные assertions).
  3. Стратегия миграции legacy-кода без freeze разработки.

Типовой техдолг​

  1. Локальные any размножаются и размывают контракты.
  2. ts-ignore остается без срока удаления.
  3. Нет ownership за типовые правила и code review стандарты.

Процесс качества​

  1. Type coverage/линты как quality gate.
  2. Checklist в PR: какие типовые риски добавлены/сняты.
  3. Документация типовых паттернов команды.

Где ломается в проде​

  1. Обновление библиотеки рушит типовую совместимость.
  2. Рефакторинг проходит компиляцию, но ломает runtime boundary.
  3. Нет механики отслеживания type debt.

Мини-задача (обязательная)​

Сделайте план миграции legacy-модуля к strict TS на 3 итерации: quick wins, среднесрочные фиксы, долгосрочные стандарты.

Что спросит интервьюер: как внедрять строгий TS в проект без остановки delivery.

Критерий готовности по уроку: вы можете управлять TS-качеством проекта как системной инженерной задачей.

Практика​

Работайте через 4 проверяемых блока. Каждый блок должен закончиться артефактом, который показывает, какой класс ошибок TypeScript реально снижает.

БлокОтветитьPractice actionReadiness
State modelingMiddle q-13, q-15Async UI state model: idle/loading/success/empty/error, discriminated union, never exhaustiveness, render mappingВы показываете impossible state, который типы запрещают, и объясняете, где всё равно нужен runtime guard
Generics and utility typesMiddle q-14, q-16Generic pagination + typed error contract: Page<T>, ApiError, selectById<T extends { id: string }>(), utility migration tableВы объясняете, где generic сохраняет связь input/output, где абстракция вредна, и почему fetchJson<T>() не валидирует payload
Boundary validationMiddle q-13..q-16, Senior q-18TypeScript boundary pack: ApiDto -> runtime validation -> DomainModel -> ViewState, invalid payload example, fallback/error behaviorВы отделяете transport DTO от domain model и показываете runtime check до доверенного типа
Strict TS at scaleSenior q-16, Senior q-20Strict 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.

Связь с треками и вопросами​

  1. Треки: Middle трек, Senior трек.
  2. Вопросы: Middle q-13..q-16, Senior q-16, Senior q-18, Senior q-20.
  3. Route practice: Middle день 4, Middle день 8, Senior день 10, TypeScript boundary pack.
  4. Повторение через 24 часа: один вопрос про type/interface, один про generics, один про discriminated union, один про DTO/runtime boundary.

Критерий готовности​

Ready, если вы используете TS не как «украшение», а как механизм снижения ошибок и ускорения безопасных изменений:

  1. state model запрещает impossible states и имеет exhaustiveness check;
  2. generic API сохраняет типовую связь без as unknown as и лишней абстракции;
  3. API boundary валидирует runtime payload до domain mapping;
  4. utility types отражают контракт, а не скрывают неясную модель данных;
  5. strict migration plan имеет owner, gates, exception policy и deadline для type debt.

Partial, если типы описаны, но нет invalid payload, runtime check или quality gate. Not ready, если ответ сводится к "добавить типы", any, as или fetchJson<T>() без проверки фактического payload.

Артефакты после модуля​

  1. Набор типовых паттернов (state, errors, events, DTO mapping).
  2. Шаблон generic pagination + error model.
  3. Boundary mapping table: ApiDto -> DomainModel -> ViewState.
  4. Invalid payload test cases and fallback behavior.
  5. Документ миграции legacy к strict TS.
  6. Набор из 6 сильных interview-ответов по TypeScript.

Куда дальше​

  1. React
  2. Frontend-архитектура
  3. TypeScript