Kodokon kodokon.com

Строгий tsconfig: ключевые опции и частые ошибки

Ты поймёшь, что на самом деле включает строгий режим, и научишься разбираться с типичными ошибками, которые он вскрывает в существующем проекте.

11 мин · 3 вопросов

Открыть этот урок в Kodokon

"strict": true - это не одна опция, а агрегат, который включает целое семейство проверок: strictNullChecks, noImplicitAny, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables и alwaysStrict. Каждая новая версия TypeScript может добавить сюда новые: включая strict, ты подписываешься и на будущие проверки. При этом любой флаг можно отключить по отдельности, что позволяет мигрировать существующую кодовую базу постепенно.

JSON
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "exactOptionalPropertyTypes": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true
  }
}
Строгая база, дополненная опциями вне агрегата (файл JSON).

Два самых влиятельных флага - strictNullChecks и noImplicitAny. Первый убирает null и undefined из всех типов: каждое потенциально отсутствующее значение нужно объявить как T | undefined и обработать до использования. Второй запрещает неаннотированному параметру молча стать any. Вместе они устраняют два больших семейства багов JavaScript: "cannot read property of undefined" и распространение непроверенных значений. useUnknownInCatchVariables дополняет картину: переменная в catch имеет тип unknown, ведь в JavaScript можно бросить что угодно.

TYPESCRIPT
function findPort(
  env: Record<string, string | undefined>
): number {
  const raw = env["PORT"];
  if (raw === undefined) {
    return 3000;
  }
  return Number.parseInt(raw, 10);
}

declare function riskyOperation(): void;

try {
  riskyOperation();
} catch (err) {
  const message =
    err instanceof Error ? err.message : String(err);
  console.error(message);
}
Два строгих паттерна: отсутствие обработано, catch как unknown.

Частые ошибки миграции и их настоящие лекарства. "Object is possibly undefined": вместо утверждения non-null !, которое лишь переносит падение в другое место, предпочитай сужение (if), опциональную цепочку ?. или значение по умолчанию через ??. "Property has no initializer" (strictPropertyInitialization): инициализируй в конструкторе, а не вешай ! на поле. Ещё берегись ловушки ?? против ||: port || 3000 заменяет также 0 и "", тогда как port ?? 3000 заменяет только null и undefined.

TYPESCRIPT
interface Settings { retries?: number }

function resolveRetries(s: Settings): number {
  const bad = s.retries || 3;
  const good = s.retries ?? 3;
  return good;
}

const names = ["Ada", "Linus"];
const first = names[0];
const upper = first?.toUpperCase() ?? "N/A";
При retries: 0 получается bad = 3, но good = 0; доступ по индексу проверяется следом.

Помимо агрегата, три опции стоит обсудить командой. noUncheckedIndexedAccess добавляет undefined к каждому доступу по индексу (arr[i], dict[key]): безопаснее, но многословно в коде, где много работы с массивами, - многие команды включают её только на новых проектах. exactOptionalPropertyTypes различает "свойство отсутствует" и "свойству присвоен undefined", что критично для патчей. skipLibCheck пропускает проверку файлов .d.ts твоих зависимостей: осознанный компромисс между временем сборки и обнаружением конфликтов между библиотеками.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что именно делает "strict": true в tsconfig.json?
    • Включает только опцию strictNullChecks
    • Включает набор строгих флагов, каждый из которых можно отключить по отдельности
    • Запрещает любое использование типа any, даже явное
    • Заставляет компилировать в режиме ES5
  2. Если retries?: number равен 0, в чём разница между retries || 3 и retries ?? 3?
    • Разницы нет, оба вернут 0
    • || вернёт 3, потому что 0 - ложное значение; ?? вернёт 0, потому что заменяет только null и undefined
    • ?? вернёт 3, потому что 0 считается отсутствующим
  3. Почему для пометки долга по типизации лучше // @ts-expect-error, чем // @ts-ignore?
    • @ts-expect-error подавляет несколько ошибок сразу
    • @ts-ignore не работает в строгом режиме
    • @ts-expect-error сам становится ошибкой, как только проблема исправлена, и долг убирается сам собой