Введение: когда компилятор говорит «нет»
Вы открываете свой любимый редактор, пишете несколько строк кода на TypeScript, запускаете сборку — и вдруг видите это: 'React' refers to a UMD global, but the current file is a module. Если вы когда-нибудь сталкивались с этой ошибкой, вы знаете, насколько она может быть обескураживающей. Вроде бы всё правильно: импорт есть, React установлен, но компилятор упрямо отказывается понимать, что вы от него хотите.
Эта ошибка — не просто случайный баг. Она раскрывает глубокие механизмы работы TypeScript, систем сборки и модульных форматов. В этой статье мы не просто покажем, как её исправить. Мы заглянем «под капот» и разберёмся, почему возникает ошибка React refers to a UMD global, как TypeScript различает UMD и ESM, и как правильно настроить ваш проект, чтобы забыть о ней навсегда.
Статья ориентирована на разработчиков, которые уже знакомы с React и TypeScript, но хотят понять внутреннюю кухню. Готовы? Поехали!
Что такое UMD и почему TypeScript о нём знает?
Прежде чем разбирать ошибку, давайте вспомним, что такое модульные форматы в JavaScript. Когда вы пишете import React from 'react', TypeScript должен понять, откуда берётся этот код и как его использовать. Существует несколько стандартов:
- ESM (ECMAScript Modules) — современный стандарт, использующий
import/export. - CommonJS — формат Node.js с
require/module.exports. - UMD (Universal Module Definition) — гибрид, который работает и в браузере (через глобальные переменные), и в Node.js (через модули).
React распространяется в нескольких форматах, включая UMD. Когда вы устанавливаете React через npm, вы получаете ESM-версию (например, index.js с export default). Но старые версии или некоторые сборки могут использовать UMD, где React доступен как глобальная переменная window.React.
TypeScript анализирует файлы деклараций типов (.d.ts). Для React это пакет @types/react. Внутри него есть объявление, которое указывает: «React доступен как UMD global». Когда TypeScript видит, что ваш файл помечен как модуль (например, содержит import или export), он начинает путаться: «Вы импортируете React, но в декларации сказано, что это глобал. А файл — модуль. Как быть?»
Почему возникает ошибка: «React refers to a UMD global, but the current file is a module»?
Ошибка появляется, когда TypeScript не может однозначно определить, как обрабатывать импорт React. Вот типичный сценарий:
// App.tsx
import React from 'react';
import { useState } from 'react';
const App: React.FC = () => {
const [count, setCount] = useState(0);
return <div>{count}</div>;
};
export default App;
Вы запускаете компиляцию, и TypeScript выдаёт ошибку: 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.
Почему? Потому что в @types/react есть такое объявление (упрощённо):
// в файле index.d.ts из @types/react
export = React;
export as namespace React;
declare namespace React {
// ...типы
}
Ключевая строка — export as namespace React. Она говорит TypeScript: «Этот модуль также доступен как глобальная переменная React (UMD)». Когда ваш файл является модулем (содержит export), TypeScript может не понять, хотите ли вы использовать глобальный React или импортированный. В результате — конфликт.
Как исправить: три проверенных способа
Способ 1: Явный импорт (самый распространённый)
Самый простой способ — убедиться, что вы явно импортируете React, даже если используете JSX. В современных версиях React (начиная с 17) JSX трансформируется автоматически, и импорт React не обязателен. Но если вы используете старую версию или настройки, добавьте:
import React from 'react';
Если ошибка остаётся, проверьте, что tsconfig.json настроен правильно:
{
"compilerOptions": {
"jsx": "react-jsx", // или "react" для старых версий
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
Параметр esModuleInterop включает совместимость между CommonJS и ESM, что часто решает проблему.
Способ 2: Отключение UMD-глобала в tsconfig
Вы можете явно запретить TypeScript использовать UMD-глобалы. Добавьте в tsconfig.json:
{
"compilerOptions": {
""types": ["react"] // только если хотите отключить глобалы
}
}
Или более жёсткий вариант — установить "skipLibCheck": true, но это отключит проверку всех типов в библиотеках, что не рекомендуется.
Способ 3: Использовать import * as React
Иногда помогает замена импорта на звёздочный:
import * as React from 'react';
Это явно указывает TypeScript, что вы работаете с модулем, а не с глобалом. Однако этот способ может нарушить tree-shaking в некоторых сборщиках, так что используйте его с осторожностью.
Как это работает под капотом: разбор деклараций TypeScript
Давайте заглянем глубже. Когда вы пишете import React from 'react', TypeScript ищет файл деклараций для пакета react. В @types/react есть два ключевых объявления:
export = React— экспорт для CommonJS/ESM.export as namespace React— объявление UMD-глобала.
Когда TypeScript видит export as namespace, он добавляет React в глобальное пространство имён. В модульном файле это вызывает двусмысленность: компилятор не знает, ссылаетесь ли вы на глобал или на импорт. Отсюда и ошибка.
Решение TypeScript — предложить добавить импорт. Но если импорт уже есть, проблема может быть в порядке разрешения модулей. Например, если ваш файл находится в папке, где TypeScript не видит node_modules из-за неправильного rootDir.
Ещё один интересный момент: ошибка часто возникает в файлах с расширением .tsx, где используется JSX. JSX-трансформация требует, чтобы React был в области видимости. Если TypeScript не может определить, отку