Обновление кодбазы React до TypeScript 6.0: что ломается в первую очередь
Как более строгие стандартные значения в TypeScript 6.0, исправление инференции методов, импорты по подпутям #/ и типы Temporal влияют на React-приложение, а также чек-лист для безопасного обновления.
Источники: TypeScript 6.0 for React Developers: What Actually Changed
Большинство обновлений компилятора проходят незамеченными. TypeScript 6.0 отличается: это последняя версия, созданная на основе оригинального компилятора JavaScript, и она в основном предназначена для подготовки кодовых баз к TypeScript 7, основанному на Go. Поэтому в ней меньше новых функций, а больше изменений, которые выявляют существующие проблемы. В этом руководстве описывается, как именно изменится работа с кодовыми базами на React, и в конце приводится чек-лист для обновления. Чтобы узнать, какое место занимает 6.0 в процессе перехода на нативный компилятор, ознакомьтесь с роль TypeScript 6 в качестве моста на пути к нативному компилятору TS 7.
Сначала более строгие настройки по умолчанию
В TypeScript 6.0 режим строгости включен по умолчанию, предполагается использование ES-модулей, а целевая версия по умолчанию изменена на es2025. Проекты, в которых каждый параметр задан явно, не пострадают; проекты, зависевшие от старых более либеральных настроек по умолчанию, столкнутся с массой новых ошибок. В коде React обычно страдают более старые компоненты: свойства с нестрогой типизацией, обработчики с имплицитными параметрами типа any, ссылки, которые могут иметь значение null, и функция useState() без аргумента типа. Это не регрессии, а давно существовавшие неоднозначности, которые теперь фиксируются.
Цели es3 и es5 также считаются устаревшими, поэтому минимальным требованием становится es2015. Для многих приложений на React это почти не меняет ситуацию, поскольку Babel, SWC или esbuild занимаются приведением синтаксиса к более старым версиям, а tsc лишь проверяет типы, но перед тем, как изменять поддерживаемые браузеры, убедитесь в этом. Выполните обновление на отдельной ветке с полным набором файлов и тестов; изменение настроек компилятора в пятницу днем — надежный способ потерять выходные.
Однородное применение правил к функциям в стиле методов
Более тонкая проблема связана с ситуацией, когда функция считается «чувствительной к контексту», то есть типы её параметров должны быть определены из контекста, прежде чем можно будет продолжить процесс вывода типов. В версии 6.0 выражение function или сокращённое обозначение метода рассматривается таким образом только тогда, когда оно действительно использует this. Раньше выбор синтаксиса метода вместо функции-стрелки мог изменить результаты вывода типов, даже если this вообще не участвовал в операциях. Приведённый ниже пример объединяет оба стиля:
// Before: inconsistent inference depending on syntax
const useCounter = (initial) => {
const [count, setCount] = useState(initial);
// this pattern used to infer differently than an arrow fn
const increment = function () {
setCount((c) => c + 1);
};
return { count, increment };
};
Ранее кэшбек, такой как increment, мог выводиться с другими типами по сравнению с эквивалентной функцией-стрелкой, и в более крупных примерах такая разница могла привести к тому, что слабый тип попадал к вызывающему коду. Теперь синтаксис, который вы выбираете для кэшбеков и возвращаемых вспомогательных функций, больше не влияет на получаемые типы, что устраняет причину путаницы, связанную с вопросами вроде «почему этот кэшбек не проверяется?».
Более чистые импорты с префиксом #/
Импорты подпутей в Node определяют приватные псевдонимы в поле imports файла package.json, и TypeScript 6.0 поддерживает префикс #/ в этом поле, соответствуя псевдонимам корня, которые часто используются бандлерами. Объявляйте сопоставление один раз:
{
"imports": {
"#/*": "./src/*"
}
}
Затем компоненты импортируют элементы из корня проекта, вместо того чтобы использовать сегменты ../:
// No more relative path spaghetti in deeply nested components
import { useAuth } from "#/hooks/useAuth";
import { Button } from "#/components/ui/Button";
Поскольку псевдоним хранится в package.json, а не только в разделе paths файла tsconfig.json, Node и TypeScript обрабатывают его одинаково, без необходимости использования дополнительных плагинов. Это работает только в режимах разрешения, которые читают импорты из package.json, таких как nodenext или bundler.
Временные типы поставляются встроенными
В TypeScript 6.0 добавлены декларации для API Temporal, который достиг заключительной стадии стандартизации в рамках TC39. Команды, разрабатывающие интерфейсы для планирования, бронирования или дашбордов, могут использовать Temporal.PlainDate или Temporal.ZonedDateTime без необходимости подключения дополнительных пакетов, вместо того чтобы иметь дело с типом Date. Однако эти типы не обеспечивают поддержки во время выполнения кода: проверьте нативную поддержку в выбранных браузерах и версиях Node и добавьте полифил, если это необходимо.
Стоит ли обновляться прямо сейчас?
Да, целенаправленно. Команда TypeScript представляет версию 6.0 как стабильное обновление, но изменения внутри действительно существуют:
- Проведите аудит файла
tsconfig.jsonи закрепите все параметры, от которых вы зависите. - Исправляйте новые ошибки строгого режима в старых компонентах, а не маскируйте их с помощью типа
any. - Уточните, какой инструмент осуществляет обратную совместимость и какая версия целевой среды ему требуется.
noImplicitOverride, чтобы для переопределенных членов требовалось использование ключевого слова override.Заключение
TypeScript 6.0 не изменит способ написания кода на React, но выявит проблемы, скрытые ранее из-за слабых стандартных настроек. Решение этих проблем сейчас, в удобное для вас время, значительно дешевле, чем делать это позже в году при переходе с версии 6 на 7, когда это станет в основном вопросом производительности.