JSX 不是字符串模板,而是语法糖
JSX 看起来像在 JavaScript 中直接写 HTML,但它既不是字符串,也不是 HTML 模板。它的本质是 React.createElement() 函数的语法糖。浏览器无法直接执行 JSX,必须通过编译器(通常为 Babel 或 TypeScript)将其转换为标准的 JavaScript 函数调用。
const element = <h1 className="greeting">Hello, world!</h1>;
经过 Babel 编译后,会变成:
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);
React.createElement 做了什么
React.createElement(type, props, ...children) 接受三个部分参数:
- type:元素类型,可以是 HTML 标签名字符串(如
'div'),也可以是组件函数或类。 - props:属性对象,包含所有的 HTML 属性和自定义属性,没有属性时传入
null。 - children:子元素,可以是字符串、数字、或者嵌套的
React.createElement调用。
调用后返回一个普通的 JavaScript 对象,也就是虚拟 DOM 节点(React Element):
{
$$typeof: Symbol(react.element),
type: 'h1',
key: null,
ref: null,
props: {
className: 'greeting',
children: 'Hello, world!'
},
_owner: null
}
这个对象就是 React 用来描述 UI 结构的最小单元。React 在渲染时会读取这棵树,递归地将其转换为真实的 DOM 节点。
新版本的 JSX 转换(React 17+)
从 React 17 开始,React 引入了新的 JSX 转换,不再需要显式地在文件中 import React from 'react'。这是因为编译器不再将 JSX 编译为 React.createElement,而是自动从 react/jsx-runtime 中引入 jsx 和 jsxs 函数。
// 源代码
const element = <h1>Hello</h1>;
// 新编译输出(自动插入 import)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { children: 'Hello' });
这个改动带来的实际好处是:
- 不再需要手动引入 React:函数组件文件中可以省略
import React from 'react',代码更清爽。 - 更好的 Tree Shaking:运行时模块可以更精细地裁剪未使用的功能。
- 为未来优化铺路:编译器可以生成更精简的代码,减少打包体积。
目前主流的构建工具(Vite、Create React App、Next.js)都已经默认启用新的 JSX 转换。
编译原理的实践启示
理解 JSX 的本质有助于避免一些常见错误:
1. 为什么 JSX 中表达式必须用花括号包裹
因为花括号内部会被作为 JavaScript 表达式直接嵌入到 React.createElement 的参数中:
<p>{name}</p> // → React.createElement('p', null, name)
2. 为什么条件渲染不能使用 if/else 语句
if/else 是语句,不是表达式,不能直接放在 JSX 的花括号内。必须使用三元表达式、逻辑与、或立即执行函数:
{isLoggedIn ? <Dashboard /> : <Login />}
{isLoading && <Spinner />}
3. 为什么组件名必须大写
编译器通过首字母大小写区分“HTML 标签”和“组件”:
- 小写 → 字符串类型:
<div>→createElement('div') - 大写 → 变量引用:
<MyComponent>→createElement(MyComponent)
如果组件名小写,React 会将其当作原生 HTML 标签处理,导致渲染出未知的 DOM 节点(如 <mycomponent>)。
4. 为什么 JSX 的属性名使用驼峰命名
JSX 本质是 JavaScript 的语法糖,它的属性会直接作为 props 对象的键名。因此必须遵循 JavaScript 的标识符规范:className 而非 class,onClick 而非 onclick,htmlFor 而非 for。
手动实现一个简单的 createElement
为了加深理解,可以自己实现一个简化版:
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === 'object' ? child : createTextElement(child)
),
},
};
}
function createTextElement(text) {
return {
type: 'TEXT_ELEMENT',
props: { nodeValue: text, children: [] },
};
}
React 的真实实现更加复杂,包含了对 key、ref 的处理、性能优化和安全性检查,但核心思路完全一致:将 JSX 编译为嵌套的 JavaScript 对象,从而构建虚拟 DOM 树。