Строгий режим TypeScript — это лишние часы в начале проекта и меньше сюрпризов потом. Вопрос в том, окупается ли обмен на коротких проектах, где половина сайтов сдаётся за месяц.
Что включает строгий режим
Флаг strict объединяет несколько проверок. Главные из них две: запрет неявного any и строгая проверка на null и undefined.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
Отдельно стоит noUncheckedIndexedAccess: он заставляет проверять результат обращения по индексу. Именно эта проверка ловит самый частый рантайм-баг — обращение к элементу пустого массива.
Сравнение двух проектов
Два корпоративных сайта похожего объёма, сданы с разницей в четыре месяца, разные команды внутри студии. Первый — без строгого режима, второй — со строгим и дополнительными флагами.
- Время разработки: 19 дней против 23 — строгий режим добавил около 20%.
- Правки по багам за три месяца после сдачи: 14 против 4.
- Из них связанных с отсутствующими данными: 9 против 0.
- Суммарно время на поддержку: 21 час против 6.
На дистанции в квартал строгий режим окупился примерно вдвое. На проекте, который сдали и забыли, он был бы чистым убытком — но таких проектов у нас почти нет: клиенты возвращаются с доработками.
Где строгость мешает
Данные из внешних источников
Ответ чужого API типизировать бессмысленно: реальность всё равно окажется другой. Мы валидируем такие данные на границе и внутрь пускаем уже проверенный объект.
import { z } from 'zod'
const OrderSchema = z.object({
id: z.string(),
total: z.number(),
items: z.array(z.object({ sku: z.string(), qty: z.number() })),
})
// Дальше по коду тип известен и гарантирован
export function parseOrder(raw: unknown) {
return OrderSchema.parse(raw)
}
Быстрые прототипы
Если задача — показать клиенту идею за два дня, строгие типы только мешают. Прототип живёт в отдельной ветке и в продакшен не едет.
Компромисс, который работает
Включаем строгий режим на новом коде и оставляем послабления для старого: в tsconfig можно указать разные настройки для разных папок. Так не приходится останавливать разработку ради миграции.
Как переводить существующий проект
- Включаем
strictи смотрим количество ошибок — обычно их сотни. - Не чиним всё сразу: добавляем
// @ts-expect-errorс комментарием и датой. - Каждую неделю разбираем часть помеченных мест.
- Запрещаем добавлять новые подавления через правило линтера.
Какие настройки дают эффект, а какие только шум
Строгий режим — это не одна галочка, а набор проверок, и они очень разные по отдаче. Главная — strictNullChecks: именно она ловит обращения к тому, чего может не быть, а это самый частый источник падений в браузере.
Дальше по полезности идёт noUncheckedIndexedAccess: обращение к элементу массива по индексу перестаёт считаться безопасным. Настройка раздражает первые дни и окупается на любом коде, который перебирает данные с сервера.
А вот noImplicitAny на существующем проекте лучше включать последним. Он даёт сотни ошибок сразу, и команда начинает расставлять any вместо типов — то есть делать ровно противоположное задуманному. Мы включаем его после того, как остальное уже зелёное.
Где типы не окупаются
Короткие лендинги (посадочная страница) с двумя формами. Проект живёт месяц, кода мало, читать его повторно никто не будет — настройка окружения займёт больше времени, чем сэкономит.
Прототипы и эксперименты. Там ценна скорость проверки гипотезы, а типы заставляют оформлять то, что завтра выкинут. Мы прямо разделяем: прототип пишется свободно, а в продукт переносится уже с типами.
Частые вопросы
Сколько стоит перевод существующего проекта?
По нашему опыту около 8–12% от объёма кодовой базы в человеко-часах, если переводить постепенно, файл за файлом. Разово и целиком — дороже и рискованнее: слишком много изменений в одном релизе (выпуск).
Заменяет ли TypeScript тесты?
Нет. Типы проверяют форму данных, тесты — поведение. Правильно посчитанная скидка неправильной формулой пройдёт проверку типов и не пройдёт тест. Одно без другого работает хуже.
Через два-три месяца проект оказывается полностью строгим, и никто не тратил на это отдельный спринт.