TypeScript основы

TypeScript давно перестал быть «надстройкой для удобства» и стал обязательным стандартом в промышленной веб-разработке. Однако поверхностное изучение его основ приводит к колоссальным проблемам на production: падение производительности, неожиданные runtime-ошибки и нечитаемый код. В этом материале мы разберём не синтаксис «по учебнику», а те аспекты, которые отличают junior-подход от профессионального. Вы узнаете, как правильно настроить компилятор, почему `any` — это антипаттерн, как работают дженерики на реальных примерах и куда смотреть, чтобы избежать типовых ловушек.
1. Настройка компилятора: strict mode и что за ним стоит
Одна из самых распространённых ошибок — использование TypeScript с минимальной конфигурацией. Разработчики оставляют `strict: false` или вообще не указывают tsconfig.json, лишая себя главного преимущества — статической проверки типов. В 2026 году любой production-проект должен работать с `strict: true` и дополнительными флагами: `noImplicitAny`, `strictNullChecks`, `noUnusedLocals` и `noUnusedParameters`. Эти настройки заставляют компилятор находить потенциально опасные места ещё на этапе разработки.
Например, без `strictNullChecks` вы спокойно передадите `null` в функцию, ожидающую строку, и ошибка проявится только в runtime. С активным strict-режимом компилятор выдаст предупреждение. Это не «придирки» — это защита от миллионов долларов убытков в enterprise-проектах. Профессиональный подход подразумевает также использование `exactOptionalPropertyTypes`, чтобы исключить путаницу между отсутствующим свойством и свойством со значением `undefined`.
2. Типы vs интерфейсы: когда что выбрать
В сообществе до сих пор идут споры, но факт остаётся фактом: в 99% случаев для описания структуры объекта лучше использовать интерфейс. Причина — возможность расширения через `extends` и более информативные сообщения об ошибках. Типы (`type`) идеальны для объединений (union), пересечений (intersection) и примитивных алиасов. Использовать тип для обычного объекта — распространённая ошибка новичков, которая усложняет поддержку кода.
Пример из практики: если вам нужно создать объект с методом, который может быть вызван в разных контекстах, интерфейс с `callable` сигнатурой даёт чёткую документацию. Типы же незаменимы при работе с условными типами (conditional types) и mapped types. Запомните простое правило: если вы описываете форму данных — используйте интерфейс; если вы вычисляете тип на основе другого типа — используйте type.
3. Дженерики: от простого к промышленному использованию
Многие разработчики останавливаются на базовых дженериках вроде `Array
Пример: функция, которая принимает массив объектов и возвращает массив значений по указанному ключу. Без дженериков пришлось бы использовать `any`, но с дженериком и ключом `keyof` код становится типобезопасным. Ошибка при передаче несуществующего ключа будет обнаружена на этапе компиляции. Для продакшена это означает сокращение количества багов на 30–40% по статистике крупных проектов. Также стоит освоить infer-вывод типов внутри условных конструкций — это открывает возможности для создания продвинутых utility-типов.
4. Utility types: как не изобретать велосипед
TypeScript предоставляет встроенные утилиты, которые решают 80% задач по трансформации типов: `Partial`, `Required`, `Pick`, `Omit`, `Record`, `Exclude`, `Extract`, `NonNullable`, `ReturnType`, `Parameters`. Тем не менее, многие пишут свои реализации, допуская ошибки и неоптимальную производительность компиляции. Использование стандартных utility types не только ускоряет разработку, но и делает код самодокументируемым.
Один из неочевидных нюансов: `Partial` делает все поля необязательными, но если у вас есть вложенные объекты, их поля остаются обязательными. Для глубокого рекурсивного `Partial` нужно либо использовать библиотеку `type-fest`, либо написать свой mapped type с рекурсией. Это частая ловушка: разработчик применяет `Partial`, а через вложенный объект всё ещё можно передать неполные данные, что приводит к runtime-ошибкам. Учитесь различать поверхностные и глубокие трансформации.
5. Типизация ошибок: reject Promise и исключения
Одна из самых опасных зон в TypeScript — обработка ошибок. По умолчанию тип ошибки в блоке `catch` — `unknown`, что вынуждает явно проверять его. Многие игнорируют это, используя `(err: any)` или `(err: Error)`. В результате приложение может упасть, если в `throw` попадёт строка, объект или `null`. Правильный подход — использовать пользовательские классы ошибок и кастомные дискриминированные объединения.
Пример: создайте интерфейс `ApiError` с полями `status`, `message`, `code`. В блоке `catch` сначала проверьте, является ли ошибка экземпляром `ApiError`, а затем уже работайте с ней. Это даёт полную типовую безопасность. Для асинхронных операций с `Promise.reject` тоже стоит типизировать reject-часть — хотя технически это не проверяется компилятором, хорошей практикой считается использование discriminated union в resolve-значении, чтобы не полагаться на reject.
6. Модули, re-exports и дерево-шейкинг
Современные бандлеры (Webpack, Vite, esbuild) отлично работают с TypeScript, но только при правильной структуре импортов. Ошибка — использовать `import * as Foo from './foo'`, если нужен только один экспорт. Это может привести к проблемам с tree-shaking и увеличению размера бандла. Вместо этого используйте именованные импорты: `import { Foo } from './foo'`.
Также стоит обратить внимание на re-exports: если вы создаёте индексный файл для группы модулей, не реэкспортируйте всё подряд. Это конфликтует с идеей дерева-шейкинга — многие бандлеры не могут оптимизировать такие реэкспорты. Лучше использовать динамические импорты или явно указывать конкретные сущности. Для больших проектов это даёт сокращение итогового JS-бандла на 10–20%.
7. Декораторы и рефлексия: когда они нужны (и когда нет)
Декораторы в TypeScript — экспериментальная функциональность, но её активно используют во фреймворках (Angular, NestJS). Важно понимать: декораторы работают только с ES-декораторами (proposal stage 3) или с экспериментальным режимом. В 2026 году уже есть финальный стандарт, и старый синтаксис считается устаревшим. Если вы пишете библиотеку, используйте новый синтаксис декораторов, иначе ваш код будет несовместим с будущими версиями.
Рефлексия на основе `reflect-metadata` — мощный инструмент для DI-контейнеров, но он добавляет runtime- зависимость. Если ваша цель — только статическая проверка, лучше обойтись без декораторов и использовать простые интерфейсы. Перегрузка кода декораторами без реальной необходимости — частая проблема, усложняющая чтение и отладку. Используйте декораторы только для строго определённых целей: внедрение зависимостей, валидация входных данных, логгирование на уровне классов.
8. Never, unknown и void: различие, которое спасает продакшен
Тип `never` — один из самых недооценённых. Он обозначает «никогда не произойдёт»: функция, которая всегда выбрасывает исключение, или бесконечный цикл. Используйте `never` для exhaustiveness checking (проверки полноты) в switch/case или discriminated unions. Если вы добавили новый вариант в объединение, компилятор укажет на незакрытый case — это предотвращает «тихие» баги, когда забывают обработать новый тип данных.
Тип `unknown` — безопасная альтернатива `any`. Если вы работаете с API, парсинг JSON или данные из внешних источников — используйте `unknown` и затем сужайте (narrow) тип с помощью type guards. `void` же возвращается функциями, которые явно не возвращают значение, но у него есть подводный камень: если объявить переменную как `void`, ей можно присвоить только `undefined` или `null` (с отключённым strictNullChecks). Правильная работа с этими тремя типами — признак высокого уровня мастерства.
Профессиональные рекомендации по изучению TypeScript
- Не начинайте с учебников 2021 года — версия TypeScript за 5 лет кардинально изменилась: появились условные типы, template literal types, новый синтаксис декораторов. Актуальная документация — на typescriptlang.org.
- Включите все strict-флаги сразу — даже если проект легаси. Это вынудит вас исправлять скрытые ошибки, которые иначе «выстрелят» позже.
- Избегайте `as any` любой ценой — каждое использование `any` ломает всю цепочку типов. Если приходится — оберните в изолированную функцию с правильным типом возврата.
- Используйте `satisfies` вместо `as` — оператор `satisfies` (появился в 4.9) проверяет, что выражение соответствует типу, но не изменяет его. Это позволяет сохранить точный тип, не теряя информацию.
- Тестируйте типы через `expectType` — используйте библиотеку `tsd` или `vitest` с ассертами типов, чтобы автоматически проверять, что ваши дженерики работают как ожидается.
Резюме
Освоение TypeScript на профессиональном уровне — это не «выучить синтаксис», а научиться мыслить типами и проектировать систему так, чтобы компилятор отлавливал как можно больше ошибок. Ключевые навыки: настройка строгой конфигурации, грамотное использование utility types, дженериков с ограничениями и exhaustive checking через `never`. Отдельное внимание уделите обработке ошибок и модульной структуре — это основа для масштабируемых проектов. Следуя этим принципам, вы не только улучшите качество кода, но и сократите время на отладку в 2–3 раза, что подтверждается опытом ведущих IT-компаний.
Добавлено: 23.04.2026
