open atlas
↑ К треку
Система типов TypeScript вглубь TS · 07 · 01

Строгость tsconfig: что на самом деле включает `strict` и какие флаги он оставляет за бортом

`strict: true` переключает восемь флагов разом; `strictNullChecks` меняет тип каждого значения, отделяя null/undefined от `T`. Главные выигрыши — во флагах вне набора: `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes` — и в постепенной миграции слабо типизированной базы.

TS Middle ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior
Уже знаешь этот юнит? Пройди быструю проверку за минуту →

Сервис месяцами работал в dev. Потом коллега поставил "strict": true в tsconfig.json, чтобы причесать новый пакет, — и весь репозиторий вспыхнул 1400 ошибками, в основном Object is possibly 'undefined'. Никто не менял логику. Один флаг разом перетипизировал каждое nullable-значение в кодовой базе. Этот флаг — strictNullChecks, и strict включил его вместе с семью братьями.

strict — один переключатель, замкнутый на восемь

strict: true не добавляет ни одной отдельной проверки — он задаёт значение по умолчанию для семейства флагов, каждый из которых при этом можно переопределить индивидуально:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true
    // подразумевает, если не переопределить каждый:
    //   strictNullChecks, strictFunctionTypes, strictBindCallApply,
    //   strictPropertyInitialization, noImplicitAny, noImplicitThis,
    //   useUnknownInCatchVariables, alwaysStrict
  }
}

Все они меняют только проверку — ни один не меняет генерируемый JS (кроме alwaysStrict, который добавляет "use strict"). Они ужесточают то, что компилятор примет, обнажая баги, которые всегда были скрыты. Паттерн переопределения важен при миграции: можно включить strict, но выборочно ослабить самый громкий флаг.

{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": false  // временно; всё остальное остаётся строгим
  }
}
флагчто ловиттипичная первая реакция
strictNullChecksnull/undefined там, где требуется ненулевое значениетысячи possibly 'undefined'
noImplicitAnyпараметр/переменная, чей тип не удалось вывести и он упал в any«добавь сюда аннотацию типа»
strictFunctionTypesнебезопасная вариантность параметров колбэка (аргументы проверяются контравариантно)обработчик типизирован слишком широко
strictBindCallApply.call/.apply/.bind вызваны с неверными типами аргументовнесоответствие this/аргументов
strictPropertyInitializationполе класса, которому никогда не присваивается значение в конструктореProperty has no initializer
useUnknownInCatchVariablesпойманная ошибка трактуется как any вместо unknownerr — это unknown, надо сузить
noImplicitThisthis неявного типа any в свободной функциианнотируй параметр this
alwaysStrict(emit) парсит файлы в strict-режиме + добавляет "use strict"почти не виден

strictNullChecks: тот, что перетипизирует всё

Без него null и undefined присваиваемы любому типу — string молча включает null. С ним они становятся собственными типами и должны моделироваться явно:

// strictNullChecks: true
function greet(name: string) {
  return name.toUpperCase(); // безопасно: name не может быть null/undefined
}

const el = document.querySelector(".btn"); // Element | null
el.addEventListener("click", handler);
//  ^^ Ошибка: 'el' is possibly 'null'.  ts(18047)
//  фикс: if (el) el.addEventListener(...)  — или el?.addEventListener(...)

Именно этот флаг создаёт стену из 1400 ошибок, потому что показывает каждое место, где старая система типов лгала. И это же — главный выигрыш по корректности во всём наборе.

Почему это работает

Почему он выключен по умолчанию вне strict? Потому что TypeScript вышел раньше, чем он появился (флаг пришёл в 2.0), и включение его на легаси-базе — нетривиальная миграция. Исходное поведение «всё nullable» — несостоятельное (unsound): оно пропускает null сквозь проверки типов. Поэтому strictNullChecks — это флаг, который заставляет систему типов действительно означать то, что она говорит.

Ценные флаги, которые strict НЕ включает

Почему важно знать, каких флагов нет в наборе? Потому что самые частые TypeScript-баги — выход за границы массива, отсутствующий ключ конфига, возвращающий undefined — живут именно в пробелах, которые набор strict не закрывает. Эти два ловят настоящие баги и не входят в набор strict — вы подключаете их отдельно:

{ "compilerOptions": { "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true } }

noUncheckedIndexedAccess делает так, что любой доступ по индексу к массиву или record возвращает T | undefined, потому что индекс может выйти за границы, а ключ — отсутствовать:

// noUncheckedIndexedAccess: true
const xs = [1, 2, 3];
const first = xs[0];   // number | undefined  (а не number!)
first.toFixed();       // Ошибка: 'first' is possibly 'undefined'.

const cfg: Record<string, string> = {};
const v = cfg.missing; // string | undefined

Он громкий, и почти каждая его жалоба — настоящий скрытый баг (off-by-one, отсутствующий ключ, доступ к пустому массиву). Цена — реальная церемония в телах циклов и при поиске; выигрыш в том, что «доступ к массиву не может быть undefined» перестаёт быть ложью.

exactOptionalPropertyTypes закрывает тонкую дыру. Без него { a?: string } и { a: string | undefined } считаются эквивалентными — то есть можно присвоить { a: undefined } «опциональному» свойству. С ним они расходятся: опциональное свойство может отсутствовать, но писать в него явный undefined нельзя.

// exactOptionalPropertyTypes: true
interface Opts { timeout?: number }       // может ОТСУТСТВОВАТЬ, а не быть undefined

const a: Opts = {};                        // ок — отсутствует
const b: Opts = { timeout: undefined };    // Ошибка: undefined не присваиваем;
//  timeout? значит «отсутствует», а не «есть и undefined»
const c: Opts = { timeout: 200 };          // ок

Это важно, когда код делает "timeout" in opts или Object.keys(opts): при слабом дефолте эти ветки ведут себя неверно, потому что { timeout: undefined } имеет ключ.

Расставь шаги по порядку

Расставь шаги миграции большой слабо типизированной базы к полной строгости без «большого взрыва» из 1400 ошибок:

  1. 1 Включи strict: true, но сразу поставь strictNullChecks: false, чтобы билд оставался зелёным
  2. 2 Сначала почини дешёвых братьев (noImplicitAny, strictBindCallApply) — мало ошибок, всё локально
  3. 3 Переключи strictNullChecks: true и чини стену null/undefined пофайлово (или по каталогам)
  4. 4 Добавь noUncheckedIndexedAccess и exactOptionalPropertyTypes последними — они обнажают самые глубокие скрытые баги
Викторина

При noUncheckedIndexedAccess: true каков тип arr[0], где const arr = [1, 2, 3]?

Вспомните перед уходом
  1. 01
    Что на самом деле делает `strict: true` и какие восемь флагов он подразумевает?
  2. 02
    Почему strictNullChecks — самый значимый флаг и что меняется, когда он включён?
  3. 03
    Что ловят noUncheckedIndexedAccess и exactOptionalPropertyTypes и почему их нет в наборе strict?
Итог

Теперь ты знаешь, что strict — мета-флаг, замыкающий восемь проверок, что strictNullChecks — тот, кто перетипизирует каждое значение, и что noUncheckedIndexedAccess и exactOptionalPropertyTypes — opt-in флаги, где прячутся самые глубокие баги. Дальше — разрешение модулей: когда проверки типов прошли, компилятору всё ещё нужно найти файлы и пакеты, которые ты импортируешь; неверный moduleResolution — классическая ловушка «работает в редакторе, падает в рантайме». Теперь, когда видишь стену possibly 'undefined' после чьего-то strict: true, ты знаешь, какой флаг сработал — и какие opt-in флаги добавить, когда пыль уляжется.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.