ПрограммированиеFrontend/Backend разработчик

Как устроена и работает Strict Function Types опция в TypeScript? Как она влияет на проверку типов функций с ковариантностью и контравариантностью, и в каких случаях несовпадение сигнатур приведёт к ошибке компиляции?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса

По умолчанию TypeScript допускает определённую "расхлябанность" при сопоставлении типовых сигнатур функций, разрешая, чтобы обратимые и необратимые функции считались совместимыми. Начиная с TypeScript 2.6 появилась опция strictFunctionTypes, гарантирующая строгую проверку типов функций и предотвращающая множество классов ошибок, особенно в крупных кодовых базах.

Проблема

Без строгой проверки возможна ситуация, когда функция-обработчик или колбэк принимает больше или более специфичный тип параметров и это незаметно для разработчика. Это приводит к runtime-ошибкам, связанным с ковариантностью возвращаемых типов и контравариантностью аргументов.

Решение

Опция strictFunctionTypes вводит строгую контравариантность для типов параметров функций. Теперь функции совместимы только если параметр типа источника является супертитом параметра назначения, а не наоборот.

Пример кода:

type Animal = { name: string }; type Cat = { name: string; meow: () => void }; let animalHandler: (a: Animal) => void; let catHandler: (c: Cat) => void; animalHandler = catHandler; // Ошибка при strictFunctionTypes: аргумент слишком специфичен catHandler = animalHandler; // Разрешено, Cat — подтип Animal

Ключевые особенности:

  • Аргументы функций проверяются на совместимость "супертимов" (контравариантность)
  • Возвращаемые значения проверяются на ковариантность (подтипы разрешены)
  • Нарушение сигнатур приводит к ошибкам компиляции

Вопросы с подвохом.

Можно ли было присвоить обработчик с более специфичным типом параметра раньше, до появления strictFunctionTypes?

Да, до активации strictFunctionTypes TypeScript позволял присваивать более специфичные функции вместо общих, что приводило к runtime-problem:

enum E { A, B } const f: (e: E) => void = (e: E.A) => {} // Без strictFunctionTypes: разрешено

Как strictFunctionTypes влияет на колбэки с опциональными параметрами?

Если параметры функции-колбэка делают какие-то параметры опциональными, строгая проверка не позволит использовать функцию с меньшим числом обязательных параметров в позиции, где ожидается функция с большим количеством. Это предотвращает ситуацию, когда callback не получает нужные данные.

Будут ли проблемы совместимости при включении strictFunctionTypes в старых проектах?

Да, есть риск появления новых ошибок компиляции, так как многие функции и обработчики могли быть присвоены друг другу с нарушением контравариантности. Чаще всего это встречается при обратных вызовах или при использовании API сторонних библиотек без строгой типизации.

Типовые ошибки и анти-паттерны

  • Использование устаревшей типизации колбэков без строгой проверки параметров
  • Попытка присвоить функцию с более специфичным/узким типом параметра более общему обработчику
  • Игнорирование ошибок при активации strictFunctionTypes (закомментировать опцию вместо исправления типов)

Пример из жизни

Негативный кейс

В большом проекте обработчики событий принимают более специфичные типы (MouseEvent вместо общего Event). Это не обнаруживается до включения строгой опции, приводя к ошибкам при запуске с разными источниками событий.

Плюсы:

  • Более быстрое прототипирование

Минусы:

  • Runtime-баги при несоответствии типов событий
  • Сложная отладка после расширения кода

Позитивный кейс

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

Плюсы:

  • Надёжность
  • Безопасность передачи функций и обработчиков
  • Предсказуемое поведение при рефакторинге

Минусы:

  • Требует тщательного проектирования сигнатур
  • В некоторых случаях приходится писать дополнительные обёртки или перегрузки для совместимости