JSX 解决了什么问题

在 JSX 出现之前,用 JavaScript 描述 UI 结构需要这样写:

const el = React.createElement(
  'div',
  { className: 'card' },
  React.createElement('h2', null, title),
  React.createElement('p', null, content),
  React.createElement('button', { onClick: handleClick }, '确认')
);

嵌套稍深就几乎无法阅读。而人类天然擅长读 HTML 这种树形标签结构。

JSX 解决的核心问题是:让 UI 结构可视化,让开发者用熟悉的标签思维描述界面,同时保留 JavaScript 的全部表达能力。

// JSX 版本,结构一目了然
const el = (
  <div className="card">
    <h2>{title}</h2>
    <p>{content}</p>
    <button onClick={handleClick}>确认</button>
  </div>
);

JSX 是什么

JSX(JavaScript XML)是一种 JavaScript 的语法扩展,允许在 JS 代码里直接写类似 HTML 的标签。

它既不是真正的 HTML,也不是模板引擎,而是 React.createElement() 的语法糖。浏览器不认识 JSX,需要由 Babel 等编译工具在构建阶段将其转换为普通 JavaScript。

// 你写的 JSX
const element = <h1 className="title">Hello, {name}</h1>;

// Babel 编译后(React 17 之前)
const element = React.createElement(
  'h1',
  { className: 'title' },
  'Hello, ',
  name
);

// Babel 编译后(React 17+ 新 JSX Transform)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { className: 'title', children: ['Hello, ', name] });

React 17 之后引入了新的 JSX Transform,编译器自动引入 react/jsx-runtime,不再需要在每个文件顶部写 import React from 'react'。

JSX 的核心规则

必须有一个根元素

// 错误:多个根元素
return <h1>Title</h1><p>text</p>

// 正确:用 Fragment 包裹,不产生真实 DOM 节点
return (
  <>
    <h1>Title</h1>
    <p>text</p>
  </>
)

用 {} 嵌入 JavaScript 表达式

<div>
  <p>{name}</p>
  <p>{isLoggedIn ? '已登录' : '未登录'}</p>
  <p>{items.length > 0 && <List items={items} />}</p>
</div>

{} 里只能放表达式(有返回值),不能放语句(if、for 等)。

属性名用驼峰命名

JSX 最终转成 JS 对象,HTML 属性名中有 JS 保留字的要重命名:

HTML 属性 JSX 属性
class className
for htmlFor
onclick onClick
tabindex tabIndex

内联样式用对象而非字符串:

// HTML
<div style="color: red; font-size: 14px">

// JSX
<div style={{ color: 'red', fontSize: '14px' }}>

标签必须关闭

// 错误
<input type="text">
<br>

// 正确
<input type="text" />
<br />

大写开头表示自定义组件

// 小写 → 原生 DOM 元素
<div>  <span>  <input />

// 大写 → 自定义 React 组件
<Button />
<UserCard name="Alice" age={30} />

JSX 不是模板语言

Vue、Angular 的模板有专属指令(v-if、v-for、*ngIf、*ngFor),JSX 没有,完全用 JavaScript 原生语法处理逻辑:

// 条件渲染
{isLoggedIn ? <UserPanel /> : <LoginButton />}
{hasError && <ErrorMessage text={error} />}

// 列表渲染
{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

这意味着 JSX 的能力上限就是 JavaScript 本身,不受模板指令集限制。

JSX 是 React 独有的吗

不是。JSX 是 Facebook(Meta)提出的开放规范,任何工具都可以实现对它的支持。目前使用 JSX 的场景:

使用 JSX 的框架和工具

框架 / 工具 JSX 支持情况
React 最主要的使用者,JSX 事实上因 React 而普及
Preact React 的轻量替代,完全兼容 JSX
Solid.js 使用 JSX,但编译策略不同,不生成虚拟 DOM
Inferno 高性能 React-like 库,使用 JSX
Stencil Web Components 框架,支持 JSX
Qwik 新兴框架,使用 JSX

不使用 JSX 的主流框架

框架 模板方案
Vue 单文件组件 .vue(<template> 标签),也可选用 JSX
Angular HTML 模板 + 指令(*ngIf、*ngFor)
Svelte 编译型模板,语法最接近原生 HTML

Vue 虽然默认用 <template>,但官方支持 JSX,可以用 render() 函数配合 JSX 写复杂组件逻辑,常见于组件库开发(如 Element Plus 部分组件)。

JSX 与 Solid.js 的差异——JSX 不等于虚拟 DOM

一个常见误解是"用了 JSX 就是在用虚拟 DOM",Solid.js 打破了这个假设。

Solid.js 同样使用 JSX,但编译器直接生成操作真实 DOM 的指令,完全跳过虚拟 DOM 的创建和 Diff 过程,性能比 React 更高:

// 同样的 JSX 写法
const App = () => <h1>Hello, {name()}</h1>;

// React 编译 → 生成虚拟 DOM → Diff → 更新真实 DOM
// Solid 编译 → 直接生成 DOM 操作代码,name() 变化时精准更新对应文本节点

这说明 JSX 只是语法层面的约定,具体的运行时行为由各框架的编译器和运行时决定。

小结

维度 说明
本质 JavaScript 语法扩展,React.createElement() 的语法糖
解决的问题 让 UI 结构可视化,兼顾 HTML 的可读性和 JS 的表达力
是否 React 独有 否,Preact / Solid / Qwik 等均支持;Vue 也可选用
是否等于虚拟 DOM 否,JSX 只是语法,虚拟 DOM 是 React 的运行时实现
编译工具 Babel、SWC、esbuild 等均可处理 JSX

JSX 和 JSP 的对比

JSX 和 JSP(JavaServer Pages)名字相近,表面上都是"把两种语言混写在一起",但本质上走了完全相反的方向。

表面相似

<%-- JSP:HTML 里嵌 Java --%>
<html>
  <body>
    <h1>Hello, <%= user.getName() %></h1>
    <% for (Item item : items) { %>
      <li><%= item.getName() %></li>
    <% } %>
  </body>
</html>
{/* JSX:JS 里嵌 HTML */}
<div>
  <h1>Hello, {user.name}</h1>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</div>

方向完全相反

维度 JSP JSX
宿主语言 HTML(嵌入 Java) JavaScript(嵌入 HTML)
运行时机 服务端,运行时解释执行 客户端,构建时编译为 JS
产物 生成 HTML 字符串推给浏览器 生成 JS 对象(虚拟 DOM)
状态管理 无状态,每次请求重新渲染整页 有状态,局部更新 DOM
混写评价 被认为是反模式(逻辑污染视图) 被认为是正确封装(组件内聚)

为什么 JSP 被抛弃,JSX 被接受

JSP 的问题是分离不彻底:HTML 模板里散落着 Java 业务逻辑,<% %> 里什么都能写,导致 Controller 逻辑泄漏进 View,最终一团乱麻。后来的解决思路是 MVC 分离,模板只负责渲染,于是有了 Thymeleaf、Freemarker 这类"无逻辑模板"。

React/JSX 的思路正好转了 180 度——不按 MVC 分层,按组件封装。一个 <UserCard> 组件把自己的 HTML 结构、交互逻辑、局部状态全部封装在一起,组件内部高度内聚,组件之间相互隔离:

MVC 思路(横向切割)          React 思路(纵向封装)

  View  (HTML 模板)           ┌─────────────┐
  ──────────────              │  UserCard   │
  Controller (逻辑)           │  - 结构(JSX)│
  ──────────────              │  - 逻辑(JS) │
  Model (数据)                │  - 样式(CSS)│
                              └─────────────┘

JSX 的"混写"是有边界的混写——只混在同一个组件内部,组件本身就是封装单元,这与 JSP 把逻辑散落在全局模板中有本质区别。

一句话:JSP 是服务端把 Java 塞进 HTML,最终输出字符串;JSX 是把 HTML 塞进 JavaScript,最终输出组件对象。前者是历史包袱,后者是架构理念从分层到组件化的演进。