React is not defined: почему JSX работает в одном проекте и падает в другом
//jsx
export default function App() {
return <h1>Привет, мир!</h1>;
}В файле нет import React from 'react'. В одном проекте компонент отображается нормально, в другом браузер сообщает:
//text
ReferenceError: React is not definedДобавление импорта может устранить ошибку. Но прежде чем менять компонент, полезно посмотреть, какой JavaScript получился из JSX. Именно здесь обнаруживается различие между старым и новым способом преобразования разметки.
При классическом преобразовании приведённый компонент превращается примерно в такой код:
//js
export default function App() {
return React.createElement(
'h1',
null,
'Привет, мир!'
);
}Вызов React.createElement появился после компиляции. В исходнике его не было, но браузер исполняет именно преобразованный вариант. Если переменная React не объявлена, выполнение останавливается с ReferenceError.
Автоматический JSX runtime генерирует другой результат:
//js
import { jsx as _jsx } from 'react/jsx-runtime';
export default function App() {
return _jsx('h1', {
children: 'Привет, мир!'
});
}Здесь функция jsx импортируется из react/jsx-runtime. Обращения к переменной React нет, поэтому отдельный импорт библиотеки ради JSX не требуется.
Новый механизм преобразования JSX появился в React 17. Однако версия самой библиотеки - не единственный фактор: результат зависит от того, какой компилятор используется и как он настроен. JSX преобразуется на этапе сборки, а не браузером во время отображения страницы.
Один и тот же текст ошибки - разные причины
Не всякая ошибка React is not defined связана с преобразованием JSX. Иногда имя используется непосредственно в исходном коде.
Например, компонент с хуком useState может выглядеть так:
//jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Нажатий: {count}
</button>
);
}При автоматическом JSX runtime этот вариант не требует импорта React по умолчанию. Но стоит добавить в разметку обращение к React.version, и ситуация меняется:
//jsx
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{React.version}: {count}
</button>
);
}React.version - обычное обращение к переменной JavaScript. Автоматический runtime не создаёт такую переменную в области видимости модуля. Импортирован только useState, поэтому при вычислении выражения возникнет ошибка.
Если версия React действительно нужна, импорт можно изменить:
//jsx
import React, { useState } from 'react';Если версия не нужна, проще удалить React.version. Здесь не требуется менять настройки Vite или TypeScript: проблема находится непосредственно в коде компонента.
Похожая ловушка встречается с фрагментами. Такой JSX использует объект React явно:
//jsx
return (
<React.Fragment>
<h1>Заголовок</h1>
<p>Описание</p>
</React.Fragment>
);При отсутствии импорта можно использовать короткую форму:
//jsx
return (
<>
<h1>Заголовок</h1>
<p>Описание</p>
</>
);В автоматическом режиме короткий фрагмент преобразуется компилятором. Вариант React.Fragment, напротив, содержит явную ссылку на переменную React и требует, чтобы она была импортирована.
Таким образом, строка из стека ошибки имеет значение. Если выполнение падает на React.version или React.Fragment, изменение режима JSX не исправит прямое обращение к необъявленному имени. Если же проблема появляется на обычной разметке, стоит исследовать результат компиляции.
Что происходит в конфигурации Vite
В стандартной конфигурации Vite плагин @vitejs/plugin-react использует автоматический JSX runtime. Минимальный пример выглядит так:
//js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});Но плагин позволяет явно включить классический режим:
//js
export default defineConfig({
plugins: [
react({
jsxRuntime: 'classic',
}),
],
});После такого изменения JSX снова преобразуется в вызовы React.createElement. Если компонент не импортирует React, а сборщик не добавляет его автоматически, возникает ошибка из-за отсутствующей переменной.
Это не означает, что jsxRuntime: 'classic' всегда неправилен. Старый проект или определённая конфигурация могут намеренно использовать классический режим. Вопрос в том, соответствует ли ему исходный код. Если настройку добавили при переносе проекта или копировании чужой конфигурации, проверьте, не изменила ли она способ преобразования JSX.
Для TypeScript аналогичный выбор есть в tsconfig.json:
//json
{
"compilerOptions": {
"jsx": "react"
}
}Значение "react" означает классическое преобразование. Для автоматического runtime используется другое значение:
//json
{
"compilerOptions": {
"jsx": "react-jsx"
}
}Есть важная оговорка: jsx управляет выводом JavaScript из файлов .tsx, обрабатываемых TypeScript. Если преобразованием занимается Vite или другой инструмент, одного изменения tsconfig.json может быть недостаточно. Нужно выяснить, какой этап сборки создаёт исполняемый код и какие настройки действуют именно на нём.
Как определить причину по стеку ошибки
Предположим, консоль показывает:
//text
ReferenceError: React is not defined
at Header (Header.jsx:18:5)Откройте Header.jsx на указанной строке. Если там написано:
//jsx
return <header>Мой сайт</header>;а ошибка возникает именно на JSX, проверьте преобразованный код. Если в нём присутствует React.createElement, хотя исходный файл не импортирует React, исследуйте конфигурацию компилятора и обработку этого файла.
Если на той же строке находится:
//jsx
const version = React.version;причина уже видна в исходнике. Переменная используется напрямую, и её нужно импортировать либо убрать это обращение.
В некоторых случаях стек указывает на собранный файл или код сторонней зависимости. Тогда помогает source map, если сборка её предоставляет. Он связывает позицию в сгенерированном JavaScript с исходным файлом. Если ошибка находится внутри пакета из node_modules, правка главного компонента приложения не устранит проблему в этой зависимости.
Переустановка React здесь не является универсальным решением. Сообщение React is not defined означает, что во время выполнения JavaScript встретил необъявленное имя. Это отличается от Cannot find module 'react', когда среда не может разрешить импорт модуля.
Для проверки полезно различать три конкретных случая:
* В исходнике есть React.version или React.Fragment - проверьте импорт либо замените обращение.
* Исходник содержит обычный JSX, но в исполняемом коде появился React.createElement - проверьте режим преобразования.
* Стек указывает на сторонний пакет - установите, какой именно код выполняется и каким образом он был собран.
Эти ситуации требуют разных исправлений. Добавлять импорт во все компоненты подряд или менять настройки сборки до определения места сбоя - значит менять код, не установив причину.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.