← Курс/Strict режим: все строгие флаги#181 из 257+20 XP

Strict режим: все строгие флаги

Что включает "strict: true"

strict: true в tsconfig.json — это сокращение, включающее группу флагов сразу:

{
  "compilerOptions": {
    "strict": true
    // Эквивалентно включению всех этих флагов:
    // "strictNullChecks": true,
    // "noImplicitAny": true,
    // "strictFunctionTypes": true,
    // "strictBindCallApply": true,
    // "strictPropertyInitialization": true,
    // "noImplicitThis": true,
    // "alwaysStrict": true,
    // "useUnknownInCatchVariables": true
  }
}

strictNullChecks — самый важный флаг

Без него null и undefined совместимы с любым типом:

// Без strictNullChecks:
let name: string = null   // OK! (но это баг)
let age: number = undefined  // OK! (но это баг)

// С strictNullChecks:
let name: string = null   // Ошибка TS!
let safe: string | null = null   // OK — явно указали null

// Теперь нужно проверять перед использованием:
function getLength(s: string | null): number {
  if (s === null) return 0
  return s.length   // TypeScript знает: s — string
}

// Оператор ?. и ?? — стали нужны:
const length = user?.name?.length ?? 0

noImplicitAny — запрет неявного any

// Без noImplicitAny:
function process(data) {  // data: any — молча
  return data.whatever   // нет ошибки TypeScript
}

// С noImplicitAny:
function process(data) {  // Ошибка TS: Parameter 'data' implicitly has 'any' type
  return data
}

function process(data: unknown) {  // OK — явный unknown
  // нужна проверка типа
}

function process(data: string[]): string {  // OK — явный тип
  return data.join(', ')
}

strictFunctionTypes — контравариантные функции

type Handler = (event: MouseEvent) => void

// Без strictFunctionTypes:
const handler: Handler = (e: Event) => {}   // OK (бивариантно — опасно)

// С strictFunctionTypes:
// const handler: Handler = (e: Event) => {}   // Ошибка TS!
// Event шире чем MouseEvent — небезопасно

const handler: Handler = (e: MouseEvent) => {}   // OK — точный тип

noUncheckedIndexedAccess — безопасный доступ по индексу

Этот флаг НЕ входит в strict, но очень полезен:

// tsconfig.json: "noUncheckedIndexedAccess": true

const arr = [1, 2, 3]
const first = arr[0]       // тип: number | undefined (а не number!)

if (first !== undefined) {
  first.toFixed(2)   // OK — проверили
}

const obj: Record<string, number> = { a: 1 }
const val = obj['b']  // тип: number | undefined

strictPropertyInitialization — инициализация полей

class User {
  name: string       // Ошибка TS: не инициализировано в конструкторе
  age!: number       // ! — говорим TS "доверяй мне, будет инициализировано"

  constructor(name: string) {
    this.name = name  // OK
    // age не инициализирован — баг!
  }
}

useUnknownInCatchVariables — безопасный catch

// Без флага:
try { /* ... */ } catch (e) {
  e.message  // e: any — нет ошибки, но небезопасно
}

// С useUnknownInCatchVariables (входит в strict):
try { /* ... */ } catch (e) {
  // e: unknown — нужна проверка типа!
  if (e instanceof Error) {
    console.log(e.message)  // OK
  }
}

Зачем включать strict

1. Выявляет скрытые баги на этапе компиляции

2. Заставляет явно описывать типы — документирует намерения

3. Делает рефакторинг безопаснее

4. Требуется для современных проектов — NestJS, Angular и другие предполагают strict

Примеры

Защитное программирование: пишем JavaScript-код который соответствует принципам strict TypeScript — явные проверки null, типов, индексов

// TypeScript strict mode запрещает неявные any, null без проверки,
// доступ без проверки и т.д. Покажем как писать "строгий" JS.

// --- strictNullChecks аналог: всегда проверяй null/undefined ---

function getLength(s) {
  // Strict TypeScript потребовал бы: if (s === null) return 0
  if (s == null) return 0
  return s.length
}

function getUser(id) {
  const users = new Map([
    [1, { id: 1, name: 'Алексей' }],
    [2, { id: 2, name: 'Ольга' }],
  ])
  return users.get(id) ?? null  // всегда явно, не undefined
}

// --- noImplicitAny аналог: явные JSDoc типы или проверки ---

/**
 * @param {string[]} items
 * @returns {string}
 */
function joinItems(items) {
  if (!Array.isArray(items)) {
    throw new TypeError(`joinItems: ожидается массив, получен ${typeof items}`)
  }
  return items.join(', ')
}

// --- noUncheckedIndexedAccess аналог: безопасный доступ по индексу ---

function safeGet(arr, index) {
  if (index < 0 || index >= arr.length) return undefined
  return arr[index]
}

function safeFirst(arr) {
  return arr.length > 0 ? arr[0] : undefined
}

function safeLast(arr) {
  return arr.length > 0 ? arr[arr.length - 1] : undefined
}

// --- useUnknownInCatchVariables аналог: безопасный catch ---

function safeJsonParse(text) {
  try {
    return { data: JSON.parse(text), error: null }
  } catch (e) {
    // В TypeScript с useUnknownInCatchVariables e: unknown
    // Проверяем тип перед использованием
    const message = e instanceof Error ? e.message : String(e)
    return { data: null, error: message }
  }
}

// --- strictFunctionTypes аналог: проверяй что передаёшь правильный callback ---

function processNumbers(arr, callback) {
  if (typeof callback !== 'function') {
    throw new TypeError('processNumbers: второй аргумент должен быть функцией')
  }
  return arr.map(item => {
    if (typeof item !== 'number') {
      throw new TypeError(`processNumbers: ожидается число, получен ${typeof item}`)
    }
    return callback(item)
  })
}

// --- strictPropertyInitialization аналог: всегда инициализируй в конструкторе ---

class UserProfile {
  // Каждое поле инициализировано — нет undefined в конструкторе
  constructor(data) {
    if (!data || typeof data !== 'object') {
      throw new TypeError('UserProfile: data обязателен')
    }
    this.id    = data.id    ?? null
    this.name  = typeof data.name === 'string' ? data.name : ''
    this.email = typeof data.email === 'string' ? data.email : ''
    this.age   = typeof data.age === 'number' ? data.age : 0
  }

  isValid() {
    return this.id !== null && this.name.length > 0 && this.email.includes('@')
  }
}

// --- Демонстрация ---

console.log('=== Проверки null (strictNullChecks) ===')
console.log(getLength(null))         // 0
console.log(getLength(undefined))    // 0
console.log(getLength('hello'))      // 5

const user = getUser(1)
if (user !== null) {
  console.log('Пользователь:', user.name)
} else {
  console.log('Пользователь не найден')
}
console.log(getUser(99))  // null — явно, не undefined

console.log('\n=== Безопасный доступ по индексу (noUncheckedIndexedAccess) ===')
const arr = [10, 20, 30]
console.log(safeGet(arr, 1))   // 20
console.log(safeGet(arr, 10))  // undefined — не ошибка
console.log(safeFirst([]))     // undefined
console.log(safeLast([1,2,3])) // 3

console.log('\n=== Безопасный catch (useUnknownInCatchVariables) ===')
const r1 = safeJsonParse('{"name":"Алексей"}')
console.log(r1.data)    // { name: 'Алексей' }
console.log(r1.error)   // null

const r2 = safeJsonParse('{invalid json}')
console.log(r2.data)    // null
console.log(r2.error)   // сообщение об ошибке

console.log('\n=== Инициализация в конструкторе (strictPropertyInitialization) ===')
const profile = new UserProfile({ id: 1, name: 'Алексей', email: 'a@b.ru', age: 30 })
console.log(profile.isValid())  // true

const empty = new UserProfile({})
console.log(empty.name)   // '' — не undefined
console.log(empty.isValid())  // false