一、引言:React 为什么可以直接写 HTML 样式的代码?
如果你刚刚开始学习 React,你可能会有这样的疑问:
"我明明在
.js
文件里写了类似 HTML 的代码,比如<div>Hello</div>
,但浏览器却能正常运行它,这是怎么回事?"
这个问题的核心在于:React 并没有真的让我们直接写 HTML,而是使用了一种叫 JSX 的语法扩展。而让浏览器能够理解这段"HTML式代码"的幕后英雄,是 Babel。
二、JSX 是什么?它是怎么来的?
2.1 JSX 简介
JSX(JavaScript XML)是一种 JavaScript 的语法扩展,允许我们在 JS 文件中像写 HTML 一样编写结构代码。
例如:
jsx
const element = <h1>Hello, world!</h1>;
这行代码看起来像是 HTML,但它其实是 JavaScript 的一种写法,最终会被转换成标准的 JavaScript 函数调用。
2.2 JSX 不是 HTML!
很多人误以为 React 可以直接写 HTML,其实不是的。我们写的 JSX 最终都会被转译为 React.createElement()
这样的函数调用。
三、Babel 是谁?它做了什么?
3.1 Babel 是一个 JavaScript 编译器
你可以把 Babel 想象成一个翻译官,它的任务是:
把你写的现代(或非标准)JavaScript 代码,翻译成浏览器可以理解的老版本 JavaScript。
比如:
- 把 ES6 的
let
和const
转换成var
- 把箭头函数
() => {}
转换成function () {}
- 把 JSX 转换成
React.createElement()
调用
3.2 Babel 如何处理 JSX?
Babel 通过插件来支持 JSX 的编译。最核心的插件就是 @babel/plugin-transform-react-jsx
。
当你写下这样的 JSX:
jsx
const element = <h1>Hello, world!</h1>;
Babel 会把它转换成:
js
const element = React.createElement('h1', null, 'Hello, world!');
是不是有点像你在用 JavaScript 创建 DOM 元素?
四、深入讲解:JSX 到底是怎么变成 JS 的?
为了更清楚地说明这个过程,我们从几个例子入手,逐步拆解。
4.1 基本元素的转换
示例 1:纯文本标签
jsx
<h1>Hello</h1>
转换后:
js
React.createElement('h1', null, 'Hello');
解释:
'h1'
:表示要创建的标签名null
:表示没有属性'Hello'
:表示子节点内容
示例 2:带属性的标签
jsx
<div className="container" id="app"></div>
转换后:
js
React.createElement('div', { className: 'container', id: 'app' });
注意:这里没有第三个参数,因为没有子节点。
示例 3:嵌套结构
jsx
<div>
<h1>Hello</h1>
<p>World</p>
</div>
转换后:
js
React.createElement(
'div',
null,
React.createElement('h1', null, 'Hello'),
React.createElement('p', null, 'World')
);
可以看到,嵌套结构变成了多个 createElement
的嵌套调用。
五、React.createElement 到底干了什么?
既然 JSX 最终都变成了 React.createElement()
,那我们就来看看这个函数到底做了什么。
5.1 函数签名
js
React.createElement(type, [props], [...children])
type
:可以是一个字符串(如'div'
),也可以是一个组件(如MyComponent
)props
:对象,表示该元素的属性children
:子节点,可以是字符串、数字,也可以是其他通过createElement
创建的元素
5.2 返回值
它返回的是一个描述 UI 的"虚拟 DOM 对象",结构大致如下:
js
{
type: 'h1',
props: {
children: 'Hello'
}
}
这个对象会被 React 用来构建 Virtual DOM,并最终渲染成真实 DOM。
六、实际开发中的工具链是如何工作的?
现在我们知道,React 项目之所以能写 JSX,是因为背后有 Babel 在默默工作。那么在实际开发中,整个流程是怎样的呢?
6.1 开发环境的构建流程图
JSX源码
↓
Babel(转换为原生JS)
↓
Webpack / Vite(打包)
↓
浏览器运行
也就是说,你的 JSX 文件在运行前就已经被完全转换成了标准的 JavaScript。
6.2 Create React App 中的 Babel 配置
如果你使用的是 Create React App,你并不需要手动配置 Babel。它已经内置了以下功能:
- 支持 JSX
- 支持最新的 JavaScript 特性
- 自动按需引入 polyfill
当然,如果你想自定义 Babel 配置,可以通过 npm run eject
或者使用 craco
工具进行修改。
七、手写一个简单的 JSX 转换器(迷你版 Babel)
为了帮助你更好地理解整个转换过程,我们来写一个非常简化的"JSX 转 JS"转换器。
7.1 输入:
jsx
<App>
<Header title="首页" />
<Content>欢迎访问</Content>
</App>
7.2 输出:
js
React.createElement(
App,
null,
React.createElement(Header, { title: "首页" }),
React.createElement(Content, null, "欢迎访问")
);
我们可以看到,所有的 JSX 元素都被转换成了 React.createElement
调用。
八、React 17 后的变化:不需要再手动引入 React
在 React 17 之前,每个使用 JSX 的文件都需要这样开头:
js
import React from 'react';
否则会报错:React is not defined
但从 React 17 开始,Babel 引入了新的 JSX 转换方式(叫做 runtime 自动导入),不再需要手动引入 React。
8.1 新的转换方式(无需 import React)
旧方式:
jsx
import React from 'react';
const element = <div>Hello</div>;
新方式:
jsx
// 不需要 import React
const element = <div>Hello</div>;
Babel 会自动将上面的代码转换为:
js
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx("div", { children: "Hello" });
也就是说,React 团队把 JSX 转换的逻辑封装到了一个新的运行时模块中,从而省去了手动导入的步骤。
九、常见问题解答
9.1 为什么不能在普通 HTML 文件里直接写 JSX?
因为普通的 HTML 文件不会经过 Babel 编译,浏览器无法识别 JSX 语法,所以会报错。
9.2 我可以在 Vue 或 Angular 项目中使用 JSX 吗?
可以!Vue 和 Angular 都支持 JSX,只需要配置好 Babel 插件即可。不过它们内部并不是用 React.createElement
,而是各自框架提供的创建函数。
9.3 使用 JSX 会影响性能吗?
不影响。JSX 只是在开发阶段使用的语法糖,最终都会被编译成标准的 JS,运行效率和手写 React.createElement
完全一致。
十、总结:JSX + Babel = 更加优雅的 React 开发体验
关键点 | 说明 |
---|---|
JSX | 是一种 JavaScript 扩展语法,用于描述 UI 结构 |
Babel | 将 JSX 转换成 React.createElement 调用 |
React.createElement | 构建虚拟 DOM 的基础函数 |
React 17+ | 不再需要手动引入 React,Babel 自动处理 |
十一、结语
刚开始接触 React 的时候,可能你会觉得写 HTML 形式的代码很神奇,甚至怀疑浏览器是不是支持了某种新特性。
但其实这一切的背后,都是 Babel 在帮你做"翻译"。正是有了它,我们才能写出更直观、更易维护的 JSX 代码,同时又不牺牲兼容性和性能。
希望这篇文章能帮你彻底搞懂 JSX 是如何工作的,以及 Babel 在其中扮演的角色。下次再有人问你:"React 是怎么用 HTML 写 JS 的?"你就可以自信地说:
"这不是 HTML,是 JSX,是 Babel 把它翻译成了真正的 JavaScript。"