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

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-качеством проекта как системной инженерной задачей.

Практика

  1. Разберите TypeScript вопросы и релевантные темы Senior Frontend.
  2. Реализуйте LoadState модель с exhaustiveness и UI-рендерингом.
  3. Сделайте generic pagination API + типизированный error contract.
  4. Прототипируйте boundary-layer с runtime validation и маппингом в domain типы.
  5. Подготовьте план strict-migration для одного legacy-модуля.
  6. Соберите типобезопасный API boundary (DTO + runtime check) в Песочнице.

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

  1. Треки: Middle трек, Senior трек.
  2. Вопросы: Middle JavaScript и React, Senior Frontend.
  3. Повторение: 3-5 вопросов без подсказок -> сверка с модулем -> повтор через 24 часа.

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

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

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

  1. Набор типовых паттернов (state, errors, events, DTO mapping).
  2. Шаблон generic pagination + error model.
  3. Документ миграции legacy к strict TS.
  4. Набор из 6 сильных interview-ответов по TypeScript.

Куда дальше

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