Skip to Content
TypeScriptГраницы и strict

Границы и strict

Уровень: middle.

Вопрос

Что включает strict, и как тип не протекает через JSON, localStorage и ответ fetch?

Короткий ответ

strict в tsconfig включает пачку флагов. На собеседовании важны три.

  • strictNullChecks: null и undefined не входят в тип сами. Поле user: User | null нельзя читать, пока нет проверки.
  • noImplicitAny: параметр без типа не становится тихим any.
  • strictFunctionTypes: колбэк нельзя подсунуть с более широким аргументом, чем ждёт вызывающий.

Компилятор стирает типы. В JS их нет, поэтому данные снаружи всегда недоверенные. response.json() по типам DOM это any или unknown в зависимости от lib. Дальше нужен разбор: zod, ручной type guard или хотя бы проверка дискриминанта. as User на сыром JSON проверкой не является.

import type и import { type User } попадают только в типы и вырезаются из бандла. Обычный импорт класса или enum остаётся в JS.

Разбор

Опциональное поле name?: string — это string | undefined, и ключ можно не передавать. Поле name: string | undefined передать нужно, даже если значение undefined. Для пропсов компонента это разный контракт.

Non-null assertion input!.value говорит «здесь точно не null». Его ставят после проверки, которую компилятор не видит, например сразу после if (!input) return в другом замыкании он уже не сужает. Чаще безопаснее Optional chaining: input?.focus().

Модуль .d.ts описывает форму без реализации: типы глобала, window, чужой пакет без своих типов. Ошибка в таком файле не ловится тестом, пока код не дойдёт до этого вызова.

На что смотрят на senior

strict выключенный в старом проекте не чинят одним коммитом. Имеет смысл включать по папкам или править границу: публичные функции модуля получают типы, внутри на время остаётся долг.

Дженерик на границе API не заменяет схему. function parse<T>(raw: unknown): T врёт: вызывающий выбирает T, а функция ничего не проверила. Честная сигнатура возвращает конкретный тип или unknown и сужает внутри.

Типичные ошибки

  • Включить strictNullChecks в голове, а в проекте его нет, и объяснять поведение не того конфига.
  • Писать as User на localStorage.getItem.
  • Импортировать тип значением и тащить в клиентский бандл модуль, который нужен только компилятору.
  • Снимать null через ! в каждой строке вместо одной проверки на входе.

Задача

Функция объявлена так. Почему вызов parse<User>(localStorage.getItem('user')) не делает значение пользователем?

function parse<T>(raw: string | null): T { return JSON.parse(raw ?? '{}') as T }
Показать ответ

Дженерик выбирает вызывающий, тело ничего не проверяет. as T только заставляет компилятор замолчать. В рантайме в хранилище может лежать null, мусор или другой объект.

Честная граница возвращает unknown или конкретный тип после проверки полей. localStorage.getItem сам по себе всегда string | null, не User.

Last updated on