📌 引言
在 React 生态系统中,JSX(JavaScript XML) 是最核心、最标志性的语法特性之一。它允许开发者在 JavaScript 文件中直接书写类似 HTML 的结构,极大地提升了 UI 构建的可读性和开发效率。
但 JSX 并不是浏览器原生支持的语法,也不是标准的 JavaScript。它背后隐藏着一套复杂的编译流程和运行时机制。本文将带你从 JSX 的语法形式 出发,深入探讨其 编译过程、底层实现原理、虚拟 DOM 构建机制,并结合一个完整的 React 组件示例,逐行分析代码逻辑,最后重点对比两种不同方式定义 React 元素的区别。
🧩 一、什么是 JSX?
JSX(JavaScript XML) 是一种 JavaScript 的语法扩展,允许我们在 JavaScript 文件中书写类似 HTML 的结构:
const element = <h1>Hello, JSX!</h1>;
尽管它看起来像 HTML,但实际上它是一种 语法糖,最终会被编译成标准的 JavaScript 函数调用,即 React.createElement()。
⚙️ 二、JSX 的编译流程
1. JSX 代码
<h1 className="title">Hello, world</h1>
2. Babel 编译后
React.createElement('h1', { className: 'title' }, 'Hello, world');
3. 编译器:Babel 的作用
- Babel 是一个 JavaScript 编译器,负责将 ES6+、JSX 等新语法转换为浏览器兼容的旧版本 JavaScript。
- 对于 JSX,Babel 会调用 @babel/plugin-transform-react-jsx 插件,将 JSX 转换为
React.createElement()调用。
4. React 17+ 的新特性:Runtime 自动引入
从 React 17 开始,你不再需要手动 import React from 'react',因为 Babel 会自动引入一个 新的 JSX 转换运行时,例如:
// 你写的
const element = <h1>Hello</h1>;
// Babel 实际生成
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx("h1", { children: "Hello" });
这使得 JSX 更加轻量,并支持更多框架使用 JSX 语法。
🧱 三、React.createElement 的工作原理
1. 函数签名
React.createElement(type, [props], [...children])
参数详解:
| 参数 | 说明 |
|---|---|
type | 元素类型,可以是字符串(如 'div')、React 组件(函数或类) |
props | 属性对象,包括 HTML 属性、事件监听器、key、ref 等 |
children | 子元素,可以是字符串、数字、React 元素,也可以是数组 |
2. 返回值:虚拟 DOM 对象
React.createElement() 返回一个描述 DOM 节点的 JavaScript 对象,即所谓的 虚拟 DOM(Virtual DOM):
{
$$typeof: Symbol(react.element),
type: 'h1',
key: null,
ref: null,
props: {
className: 'title',
children: 'Hello, world'
},
_owner: null,
_store: {}
}
这个对象会被 React 用来与旧的虚拟 DOM 做 diff 算法,计算出最小的 DOM 更新操作。
🧪 四、完整代码解析:App 组件详解
我们来看一个完整的 React 组件示例,逐步解析其结构和运行机制。
import { useState, createElement } from 'react'
import './App.css'
function App() {
const [todos, setTodos] = useState([
{ id: 1, title: '标题一' },
{ id: 2, title: '标题二' },
{ id: 3, title: '标题三' }
])
const element = <h1 className='title'>Hello,world</h1>
const elememt2 = createElement('h1', { className: 'title', id: 'tit' }, 'Hello,world')
return (
<>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
<ul>
{todos.map((todo) => (
createElement('li', { key: todo.id }, todo.title)
))}
</ul>
{element}
{elememt2}
</>
)
}
export default App
🔍 1. 状态管理:useState
const [todos, setTodos] = useState([...])
- 使用
useState定义了一个状态变量todos,表示待办事项列表。 - 初始化了三个待办事项对象,每个都有
id和title。 - React 会自动跟踪该状态的变化,并在变化时重新渲染组件。
🔍 2. JSX 元素定义
const element = <h1 className='title'>Hello,world</h1>
- 使用 JSX 定义了一个标题元素。
- 通过
className设置样式类名。 - 实际上会被编译为:
React.createElement('h1', { className: 'title' }, 'Hello,world')
const elememt2 = createElement('h1', { className: 'title', id: 'tit' }, 'Hello,world')
- 显式使用
React.createElement()创建元素。 - 除了
className,还额外添加了id="tit"属性。 - 适用于需要动态构建元素属性的场景。
🔍 3. 渲染部分详解
第一组 <ul>:使用 JSX 渲染列表
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
- 使用 JSX 的
<li>标签渲染每个待办事项。 key是 React 用于优化渲染的唯一标识符,必须唯一。
(关于key相关内容可以返回阅读上一篇博客)
第二组 <ul>:使用 createElement 渲染列表
<ul>
{todos.map((todo) => (
createElement('li', { key: todo.id }, todo.title)
))}
</ul>
- 使用
createElement()手动创建<li>元素。 - 功能与第一组完全一致,但写法更底层。
最后渲染两个标题元素
{element}
{elememt2}
element是 JSX 创建的标题。elememt2是createElement创建的标题。- 两者都会被 React 正确渲染为
<h1>元素。
📊 五、JSX 与 createElement() 的对比分析
| 特性 | JSX | React.createElement() |
|---|---|---|
| 语法风格 | 类似 HTML,更直观 | 函数式写法,更底层 |
| 可读性 | 高,适合 UI 结构 | 较低,适合动态逻辑 |
| 灵活性 | 适合静态结构 | 更适合动态生成元素 |
| 是否需要编译 | 是(需 Babel) | 否 |
| 是否支持属性扩展 | 支持(如 key, className) | 完全支持,可自由控制 |
| 适用场景 | UI 构建、组件结构 | 动态元素生成、高阶组件、插件开发 |
✅ 示例对比
// JSX
const element = <h1 className="title">Hello, world</h1>
// 编译后
const element = React.createElement('h1', { className: 'title' }, 'Hello, world')
// createElement
const elememt2 = React.createElement('h1', { className: 'title', id: 'tit' }, 'Hello, world')
两者最终都会被 React 渲染为相同的虚拟 DOM 对象,但在开发阶段,JSX 更加直观和简洁。
🧠 六、虚拟 DOM 的构建与更新机制
1. 虚拟 DOM 的构建
React 通过 React.createElement() 创建的虚拟 DOM 对象构建一棵虚拟 DOM 树。例如:
const vdom = React.createElement('ul', null,
React.createElement('li', { key: 1 }, '标题一'),
React.createElement('li', { key: 2 }, '标题二'),
React.createElement('li', { key: 3 }, '标题三')
);
这个虚拟 DOM 树会被 React 用来与上一次的虚拟 DOM 进行比较,计算出最小的 DOM 更新操作。
2. Diff 算法
React 使用高效的 diff 算法 对比新旧虚拟 DOM:
- 层级对比:只对比同一层级的节点。
- 列表 key 优化:通过
key属性识别元素的唯一性,提升列表更新效率。 - 最小化 DOM 操作:只更新真正变化的部分,避免全量重绘。
3. 真实 DOM 的更新
一旦 React 确定需要更新的节点,它会通过原生的 document.createElement()、appendChild()、removeChild() 等方法更新真实 DOM。
🎨 七、总结与建议
- JSX 是 React 的核心语法,它让 UI 开发变得直观、高效。
React.createElement()是 JSX 的底层实现方式,适用于更底层的开发场景。- 在日常开发中推荐使用 JSX,但在高阶组件、动态渲染、插件开发等场景中,
createElement会更有优势。 - 理解 JSX 的编译过程和底层原理,有助于写出更高效、更清晰的 React 代码。
- React 的虚拟 DOM 和 diff 算法是其性能优化的关键,理解其机制有助于我们写出更高效的组件。
📄 结语
通过本文,我们不仅了解了 JSX 的基本概念和底层原理,还通过实际代码深入对比了 JSX 与 React.createElement() 的异同。无论你是初学者还是有经验的开发者,理解这些内容都将帮助你更好地掌握 React 开发的精髓。
欢迎留言交流你对 JSX 的看法,或者分享你在项目中使用 JSX 的最佳实践!