TypeScript давно перестал быть нишевым выбором и стал обычным способом писать фронтенд и серверный код на Node.js. Но польза от него не появляется автоматически: типы можно расставить так, что они будут только мешать, а можно так, что редактор начнёт находить ошибки за разработчика. Ниже — как мы решаем, нужен ли TypeScript проекту, как переводим на него существующий код и чего от типизации ждать не стоит.
Что типизация даёт на практике
Главное отличие от JavaScript — типы проверяются до запуска. Компилятор видит, что функция ждёт число, а получает строку, что у объекта нет поля, к которому обращаются, что ветка условия недостижима. Всё это в JavaScript выясняется в рантайме, чаще всего у пользователя.
Второй эффект менее очевиден и в больших проектах важнее первого: типы работают как документация, которая не устаревает. Разработчик, впервые открывший модуль, видит, что принимает и возвращает функция, без чтения её тела и без поиска примеров вызова. Редактор на этих же данных строит автодополнение, переход к определению и переименование по всему проекту — рефакторинг перестаёт быть поиском по строкам.
Третий эффект — контракты между частями системы. Интерфейсы фиксируют договорённость между модулями, и при её нарушении ломается сборка, а не поведение в проде.
Когда оправдан, а когда избыточен
Мы предлагаем TypeScript, когда выполняется хотя бы часть условий: над кодом работает больше одного человека, проект живёт дольше нескольких месяцев, в нём много моделей данных и переходов между экранами, планируется передача другой команде. Чем дольше срок жизни кода, тем выгоднее вложение в типы.
Избыточен он там, где кода мало и он одноразовый: небольшой лендинг с формой и парой анимаций, скрипт для разовой выгрузки, прототип, который проверяет гипотезу и будет выброшен. Настройка сборки и типизация в таком случае съедят больше времени, чем сэкономят.
Отдельный случай — команда без опыта работы с типами. Тогда переход стоит планировать вместе с обучением: иначе появляется код, где половина значений объявлена как any, а проверка типов формально включена и ничего не находит.
Что TypeScript закрывает и что остаётся на вас
| Проблема в JavaScript | Как закрывает TypeScript | Что всё равно надо проверять |
|---|---|---|
| Опечатка в имени свойства объекта | Ошибка компиляции при обращении к несуществующему полю | Данные извне: там поля может не быть вовсе |
| Функция вызвана не с теми аргументами | Проверка сигнатуры на месте вызова | Правильность самой логики функции — тестами |
| Обращение к значению, которого нет | Строгие проверки на null и undefined в strict-режиме | Асинхронные состояния: значение может исчезнуть между проверкой и использованием |
| Неполная обработка вариантов | Размеченные объединения и проверка исчерпанности вариантов | Новые варианты, приходящие с сервера, но не описанные в типах |
| Незаметная поломка при рефакторинге | Переименование и поиск использований по всему проекту | Динамические вызовы по строковым именам |
| Неизвестный формат ответа сервера | Типы описывают ожидаемую структуру данных | Фактический ответ: типы стираются при сборке и в рантайме ничего не проверяют |
Миграция существующего проекта по шагам
Переписывать проект целиком в одну итерацию почти всегда плохая идея: получается большой нерабочий этап без промежуточного результата. Мы идём постепенно.
- Подключаем TypeScript и конфигурацию сборки, разрешив смешанный код: файлы .js и .ts живут рядом.
- Включаем проверку типов на существующих JS-файлах в мягком режиме и смотрим, что найдёт компилятор без единой правки.
- Описываем модели данных — то, что ходит между клиентом и сервером. Это даёт больше всего пользы на единицу усилий.
- Переводим утилиты и общие модули: у них много вызывающего кода, и типы сразу начинают работать по всему проекту.
- Переводим слои интерфейса, экран за экраном, вместе с плановыми правками по задачам, а не отдельным проектом.
- Постепенно ужесточаем настройки и убираем временные послабления, оставленные на первых шагах.
Полезное правило: запрещаем добавлять новые JS-файлы с самого начала миграции. Иначе доля типизированного кода не растёт, потому что новое пишут по старой привычке.
Строгий режим
Без strict-режима TypeScript теряет значительную часть смысла: без строгой проверки null любое значение можно считать существующим, и самый частый класс ошибок остаётся неотловленным. На новом проекте strict включается сразу. На унаследованном — по одной опции за раз, начиная с проверок на null и на неявный any.
Ещё две привычки, которые окупаются: запрет на any в новом коде, кроме явно помеченных мест, и запрет на подавление ошибок комментарием без пояснения. Подавленная ошибка без причины — это не решение, а отложенная поломка.
Данные с бэкенда: типы не проверяются в рантайме
Самое частое заблуждение: если описать тип ответа сервера, данные будут гарантированно такими. Это не так. Типы существуют только на этапе компиляции и при сборке стираются полностью. Если сервер вернёт другое поле, другой формат даты или null вместо объекта, код упадёт ровно так же, как упал бы на чистом JavaScript.
Поэтому на границе системы нужна проверка в рантайме. Практический подход:
- Описываем схему ответа один раз и получаем из неё и тип, и функцию проверки — так описание не расходится с реальностью.
- Проверяем данные сразу после получения, а не в момент отображения. Ошибка должна возникать там, где известна причина.
- Отдельно обрабатываем случай «поле отсутствует» и случай «поле пустое»: для пользователя это разные ситуации.
- Логируем расхождение схемы, а не молча подставляем значение по умолчанию — иначе изменение на стороне сервера останется незамеченным.
То же касается данных из адресной строки, форм, локального хранилища и сторонних виджетов. Всё, что пришло извне, — это unknown, пока не проверено.
Типы для чужих библиотек
У большинства популярных пакетов типы поставляются вместе с кодом или отдельным пакетом с описаниями. Проблемы начинаются со старых или узких библиотек, где типов нет. Варианты действий здесь такие: написать собственное описание для той части, которой пользуетесь, обернуть библиотеку своим модулем с типизированным интерфейсом или отказаться от неё, если она давно не обновляется.
Писать описание на весь пакет не нужно — достаточно вызовов, которые реально используются в проекте. И стоит помнить, что чужие типы могут расходиться с фактическим поведением библиотеки: они пишутся людьми и тоже устаревают.
Сборка и скорость компиляции
На крупном проекте проверка типов заметно влияет на время сборки. Ускоряют её несколько приёмов: разделение на проекты со ссылками между ними, отделение проверки типов от преобразования кода, инкрементальная сборка с кэшем. В процессе разработки проверку удобно держать отдельным фоновым процессом, чтобы она не блокировала пересборку интерфейса.
Обязательный пункт — проверка типов в непрерывной интеграции. Если она есть только в редакторе разработчика, ошибки будут попадать в основную ветку от того, кто просто не заметил подчёркивание.
Чего TypeScript не делает
Типы не проверяют логику. Функция, складывающая вместо вычитания, полностью корректна с точки зрения компилятора. Не спасают они и от ошибок доступа к данным, гонок состояний, неверных прав, падений сети. Не заменяют тесты: типы отвечают на вопрос «согласованы ли части кода между собой», тесты — на вопрос «делает ли код то, что нужно».
И типы не улучшают архитектуру сами по себе. Запутанную структуру можно аккуратно типизировать, и она останется запутанной — просто с описанием.
Если нужно собрать проект на TypeScript с нуля или аккуратно перевести существующий код без остановки разработки, это наша работа: создание и разработка сайтов. Оценим объём, предложим порядок миграции и возьмём на себя настройку сборки и проверок.











