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

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