
理解JSX Pragma及其作用
在react开发中,jsx允许我们在javascript代码中编写类似html的结构。然而,浏览器并不能直接理解jsx。在代码执行之前,jsx需要通过babel等工具进行转换。这个转换过程会将jsx元素编译成常规的javascript函数调用。
默认情况下,Babel会将JSX元素编译为React.createElement()函数的调用。例如,
/** @jsx pragma */ 是一种特殊的注释,被称为JSX Pragma。它指示Babel JSX转换插件使用哪个函数来编译JSX表达式,而不是默认的React.createElement。当你看到/** @jsx jsx */时,它告诉Babel将所有的JSX元素编译为对一个名为jsx的函数的调用,而不是React.createElement。
这种自定义JSX Pragma的场景通常出现在需要特定JSX运行时行为的库中,例如:
- Emotion库的css prop: Emotion允许你使用css prop来直接在组件中定义样式。为了支持这种语法,它需要一个自定义的JSX运行时,通常是@emotion/react包提供的jsx函数。
- Preact: Preact使用h函数来创建虚拟DOM节点,而不是React.createElement。
“'jsx' must be in scope”错误解析
当你在文件顶部添加了/** @jsx jsx */ Pragma,但没有在文件中导入名为jsx的函数时,就会触发“'jsx' must be in scope when using JSX”的ESLint警告或“jsx is not defined”的编译错误。
- ESLint警告 (eslintreact/react-in-jsx-scope): ESLint在进行静态代码分析时,会识别到/** @jsx jsx */ Pragma。它知道这个Pragma意味着代码中将使用一个名为jsx的函数来处理JSX。如果ESLint发现这个jsx函数没有被导入到当前作用域中,它就会发出警告,提醒你可能存在潜在的运行时错误。
- 编译错误 (jsx is not defined): 即使你通过配置'react/react-in-jsx-scope': 'off'禁用了ESLint的警告,底层的问题依然存在。当Babel根据/** @jsx jsx */将JSX编译为jsx(...)调用时,如果运行时环境中没有定义jsx函数,JavaScript引擎就会抛出ReferenceError: jsx is not defined的错误,导致应用无法编译或运行。
简单来说,/** @jsx jsx */就像一个承诺,告诉编译器“我将提供一个名为jsx的函数来处理JSX”。如果你没有兑现这个承诺(即没有导入jsx),那么运行时就会出错。
解决方案
解决此问题主要有两种方法,取决于你的实际需求:
方案一:导入自定义JSX工厂函数 (当你确实需要自定义JSX运行时)
如果你正在使用像Emotion这样的库,并且明确需要其自定义的JSX运行时(例如,为了使用css prop),那么你必须从相应的包中导入jsx函数。
示例代码:
/** @jsx jsx */ // 告诉Babel使用jsx函数编译JSX
import { createContext, useContext, useState } from 'react';
import { jsx } from '@emotion/react'; // 关键:导入Emotion提供的jsx函数
interface MyContextType {
isReady: boolean;
}
interface Props {
children: React.ReactNode;
}
const MyContext = createContext({} as MyContextType);
export const MyContextProvider = ({ children }: Props) => {
const [isReady, setIsReady] = useState(false);
return (
{children}
);
};
// 示例用法 (假设某个组件需要使用Emotion的css prop)
const StyledDiv = () => (
这是一个使用Emotion样式化的div。
);注意事项:
- 确保你已经安装了对应的库(例如@emotion/react)。
- import { jsx } from '@emotion/react'; 必须出现在使用/** @jsx jsx */ Pragma的文件的顶部,以便jsx函数在编译时和运行时都可用。
方案二:移除自定义JSX Pragma (当你不需要自定义JSX运行时)
如果你没有使用Emotion或其他需要自定义JSX运行时的库,或者你仅仅是错误地添加了/** @jsx jsx */ Pragma,那么最简单的解决方案就是将其删除。
通过删除/** @jsx jsx */,你将指示Babel恢复其默认行为,即使用React.createElement()来编译JSX。
示例代码:
// 移除了 /** @jsx jsx */ Pragma
import { createContext, useContext, useState } from 'react';
import React from 'react'; // 在旧版React或需要使用React Hooks/Context时仍需导入React
interface MyContextType {
isReady: boolean;
}
interface Props {
children: React.ReactNode;
}
const MyContext = createContext({} as MyContextType);
export const MyContextProvider = ({ children }: Props) => {
const [isReady, setIsReady] = useState(false);
return (
{children}
);
};注意事项:
- 在React 17及更高版本中,随着新的JSX转换(New JSX Transform)的引入,即使不导入React,JSX元素也能被编译。然而,如果你在组件中使用了React Hooks(如useState, useContext)或React Context,你仍然需要import React from 'react',因为这些API是React对象的一部分。
- 这种方法适用于绝大多数标准的React应用。
总结与最佳实践
“'jsx' must be in scope”错误是一个清晰的信号,表明你的JSX Pragma配置与实际代码中的JSX运行时不匹配。解决这个问题的关键在于理解/** @jsx pragma */的作用以及它如何影响Babel的JSX编译过程。
- 明确意图: 在使用/** @jsx pragma */之前,请明确你是否真的需要自定义JSX运行时。如果你只是在编写标准的React组件,通常不需要这个Pragma。
- 保持一致性: 如果你使用了自定义Pragma(例如/** @jsx jsx */),请务必在同一文件中导入相应的JSX工厂函数(例如import { jsx } from '@emotion/react';)。
- 避免禁用ESLint规则: 像'react/react-in-jsx-scope': 'off'这样的ESLint规则通常是为了捕获潜在的运行时错误。禁用它们只会隐藏问题,而不是解决问题。始终尝试理解并修复ESLint报告的根本原因。
- 了解项目配置: 检查你的babel.config.js或webpack.config.js等构建配置,了解JSX是如何被转换的,这有助于诊断更复杂的JSX相关问题。
通过遵循这些指南,你可以有效地避免和解决React应用中与JSX Pragma相关的“'jsx' must be in scope”错误,确保代码的健壮性和可维护性。










