什么是JSX
在开发React项目时,我们都会在JSX文件中编写组件。但是JSX文件在js环境中运行会发生报错,这是因为在React项目中,在编译阶段Babel就已经将JSX结构处理成ReactElement格式。
通过Babel的官方网站,我们可以体验实时的编译功能,可以直接输入 JSX 代码查看转换结果。 可以看到,JSX结构已经被编译成了React.createElement形式。
React.createElement
createElement在react/index下被导出:
js
// package/react/index.js
export { createElement } from '.src/ReactClient';
在react/src/ReactClient下被引入:
js
// package/react/src/ReactClient
import {
createElement,
} from './jsx/ReactJSXElement';
最后我们就能找到createElement定义的地方,在ReactJSXElement
这个文件中
js
// package/react/src/jsx/ReactJSXElement
export function createElement(type, config, children) {
// ......
}
入参解读
现在让我们一起来看一下React.createElement这个api的用法
React.createElement( type, config, children )
- 第一个参数type:如果是组件类型,会传入组件对应的类或函数;如果是 DOM 元素类型,则传 入 div 或者 span 之类的字符串。
- 第二个参数config:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。
- 其他参数children:依次为该元素的子元素,根据顺序排列。返回结果为 React Element 对象。
createElement是怎么工作的
createElemt的工作可以大致分为以下四步:
- 初始化参数,type、config
- 处理config中的其他属性
- 处理children,形成扁平化的children数组
- 创建React Element对象
看一下代码的大致实现:
js
/**
* 创建 React 元素的核心函数
* @param {string|Function} type - 元素类型,可以是原生标签字符串(如'div')或 React 组件
* @param {Object|null} config - 包含元素属性的配置对象
* @param {...any} children - 子元素,可以是任意数量的参数
* @returns {Object} - 返回一个 React 元素对象
*/
export function createElement(type, config, children) {
/* 1. 初始化参数,type、config */
let propName; // 用于遍历属性名的临时变量
// 创建一个空对象来存储处理后的 props
const props = {};
let key = null; // 初始化 key 为 null
// 如果传入了 config 对象(即属性对象不为空)
if (config != null) {
// 检查并处理有效的 key
if (hasValidKey(config)) {
key = '' + config.key; // 将 key 强制转换为字符串
}
/* 2. 处理config中的其他属性 */
// 将剩余的属性添加到新的 props 对象中
for (propName in config) {
// 检查是否是 config 自身的属性(非原型链上的)
if (
hasOwnProperty.call(config, propName) &&
// 跳过保留的属性名
propName !== 'key' && // key 已经单独处理
// 尽管我们在运行时不再使用这些属性,但我们不希望它们出现在 props 中,
// 所以在 createElement 中过滤掉它们。
// 在 jsx() 运行时不需要这样做,因为 jsx() 转换从不将这些作为 props 传递;
// 它使用单独的参数。
propName !== '__self' &&
propName !== '__source'
) {
props[propName] = config[propName]; // 将属性复制到 props 对象
}
}
}
/* 3. 处理children,形成扁平化的children数组 */
// 处理子元素:可以有多个参数,这些参数会被转移到新分配的 props 对象上
const childrenLength = arguments.length - 2; // 计算子元素数量,函数入参减去type和config即为子元素数量
if (childrenLength === 1) {
// 如果只有一个子元素,直接赋值给 props.children,这种情况下一般为文本节点
props.children = children;
} else if (childrenLength > 1) {
// 如果有多个子元素,创建一个数组来存储它们
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2]; // 收集所有子元素
}
props.children = childArray; // 将子元素数组赋值给 props.children
}
// 解析默认 props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
// 只有当 props 中该属性为 undefined 时才应用默认值
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
/* 4. 创建React Element对象 */
// 创建并返回 React 元素对象
return ReactElement(
type, // 元素类型
key, // 元素的 key
undefined, // ref(在这里未处理,会在 ReactElement 函数中处理)
undefined, // 未使用
getOwner(), // 获取当前所有者(用于上下文和调试)
props, // 处理后的 props 对象
);
}
总结
由此可见,JSX其实是React.createElement
语法糖,而createElement
作为一个函数,其实就是根据入参,返回一个Reacte Element
对象。
React Element
JSX在编译阶段会转换成createElemnt
的形式,执行createElement
则会生成React ELement
对象,该对象包含包含以下属性:
js
const element = {
$$typeof: REACT_ELEMENT_TYPE, // Element 元素类型,Symbol值
type: type, // type 属性,证明是DOM元素还是自定义组件
key: key, // key 属性
ref: ref, // ref 属性,获取对实际 DOM 节点或组件实例的引用
props: props, // props 属性,包含元素的属性和子元素
...
};
在React项目中打印一个JSX元素
js
const AppJsx = (
<div id='root' className='root'>
<span className='span'>123</span>
<p id='p'>this is p</p>
'today is Sunday'
</div>
)
console.log(AppJsx);
在控制台中可以得到:

Babel工作流程
JSX语法的编译实现是由Babel插件实现的:
@babel/plugin-syntax-jsx
:仅允许 Babel 解析 JSX 语法(不转换),为后续插件提供 AST 支持@babel/plugin-transform-react-jsx
:将 JSX 转换为React.createElement()
或jsx()
调用
两种Runtime的插件实现
- 经典运行时
Classic Runtime
- 要求:必须在文件中显式导入
React
(否则会报错) - 转换结果:JSX →
React.createElement()
js
function App() {
return /*#__PURE__*/React.createElement("div", {
id: "root",
class: "root"
}, /*#__PURE__*/React.createElement("span", null, "123"), /*#__PURE__*/React.createElement(Child, null), "'today is Sunday'");
}
- 自动运行时
Automatic Runtime
- 优势: 无需手动导入
React
- 转换结果:JSX →
jsx()
或jsxs()
(从react/jsx-runtime
自动导入)
js
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
function App() {
return /*#__PURE__*/_jsxs("div", {
id: "root",
class: "root",
children: [/*#__PURE__*/_jsx("span", {
children: "123"
}), /*#__PURE__*/_jsx(Child, {}), "'today is Sunday'"]
});
}
- 配置:
js
{
"presets": [
[
"@babel/preset-react",
{
"runtime": "automatic",
}
]
]
}