JSX 与 JavaScript 的关系:从语法糖到生态系统
Hi,我是前端人类学(之前叫布兰妮甜)!
“JSX 既不是 HTML,也不是字符串;它只是 JavaScript 的一种更友好的写法。” ——React 官方文档
在 2025 年的前端开发语境里,JSX 几乎成了 React 的代名词。然而,很多初学者仍然把它当成“另一种语言”。本文尝试用一条清晰的脉络回答:JSX 与 JavaScript 到底是什么关系
?它们如何分工、如何协同,以及在实际工程里有哪些最新实践。
文章目录
- 一、血统:JSX 首先是一段合法的 JavaScript 程序
- 1.1 语法扩展,而非新语言
- 1.2 文件扩展名只是“提示”
- 二、转译:从 JSX 到 JavaScript 的三次“变形”
- 2.1 经典转换:`React.createElement`
- 2.2 新 JSX Transform:React 17+ 的优化
- 2.3 TypeScript 中的 JSX 模式
- 三、语义:JSX 让 JavaScript 获得了“声明式 UI”表达能力
- 3.1 声明式 vs 命令式
- 3.2 JSX 的 JavaScript 完全能力
- 四、边界:JSX 做不到的那些 JavaScript 事
- 五、生态:2025 年的工程化实践清单
一、血统:JSX 首先是一段合法的 JavaScript 程序
1.1 语法扩展,而非新语言
JSX 是 ECMAScript 的 语法扩展 (syntax extension)。按照规范,任何符合 JSX 语法的源码片段 在词法和语法层面都是合法的 JavaScript。因此:
- 它不会引入新的关键字,也不会破坏
var / let / const
的词法作用域规则; - 所有 JavaScript 表达式在 JSX 中依旧可用(花括号
{}
内); - 任何 JavaScript 运行时可以忽略 JSX 语法,直接报错或跳过(因为不认识的 AST 节点)。
1.2 文件扩展名只是“提示”
.jsx
与 .js
的差异并非规范强制,而是工程约定:
扩展名 | 典型用途 | 是否需要编译 | 备注 |
---|---|---|---|
.js | 普通 JavaScript、工具函数、无 JSX 的 React 组件 | 可选 | 若文件里写了 JSX,则仍需 Babel/TypeScript 处理 |
.jsx | 包含 JSX 的 UI 组件 | 必须 | 让开发者、IDE、Linter 一眼看出“这是 JSX” |
二、转译:从 JSX 到 JavaScript 的三次“变形”
浏览器、Node.js 本身并不认识 JSX,因此源码上线前需要“翻译”。当前主流工具链有 Babel、TypeScript Compiler (tsc)、esbuild、swc,它们做的事可以概括为三步:
阶段 | 输入 | 输出 | 工具 |
---|---|---|---|
① 类型擦除 | TSX (TypeScript + JSX) | JSX | tsc --jsx preserve |
② JSX 变换 | JSX | React.createElement / _jsx() 调用 | Babel @babel/plugin-transform-react-jsx |
③ 语法降级 | ES2025 | ES2015 / ES5 | Babel preset-env / swc |
2.1 经典转换:React.createElement
这是 React 16 及以前的默认做法:
// 源码
function Button({ label }) {return <button>{label}</button>;
}// Babel 产物 (旧)
import React from 'react';
function Button({ label }) {return React.createElement('button', null, label);
}
2.2 新 JSX Transform:React 17+ 的优化
2020 年底,React 团队引入 “新 JSX 转换”:
// Babel 产物 (新)
import { jsx as _jsx } from 'react/jsx-runtime';
function Button({ label }) {return _jsx('button', { children: label });
}
带来的好处:
- 无需手动
import React
(除非使用 Hooks); - 包体更小:每个文件减少一次
React
依赖; - 运行时更快:
_jsx
内部可以跳过一些createElement
的兼容逻辑。
2.3 TypeScript 中的 JSX 模式
tsc
提供 4 种 --jsx
选项,决定了第 ① 步的产物:
preserve
:完全保留 JSX,交给 Babel 做后续;react
:直接编译成React.createElement
;react-jsx
:输出_jsx
调用;react-jsxdev
:开发模式,额外注入调试信息。
三、语义:JSX 让 JavaScript 获得了“声明式 UI”表达能力
3.1 声明式 vs 命令式
在纯 JavaScript 里,更新 DOM 通常是命令式:
const h1 = document.createElement('h1');
h1.textContent = 'Hello';
document.body.appendChild(h1);
JSX 把同样的意图声明式地写进返回值:
function Greeting() {return <h1>Hello</h1>;
}
React 负责把这段“描述”同步到真实 DOM。开发者不再关心如何更新,只关心长什么样。
3.2 JSX 的 JavaScript 完全能力
由于 JSX 只是语法糖,你可以在标签属性、子元素中写任意 JavaScript:
function Card({ user }) {return (<div className={user.isAdmin && 'admin'}><img src={user.avatar} alt={`${user.name}'s avatar`} />{user.posts.map(p => <Post key={p.id} data={p} />)}</div>);
}
四、边界:JSX 做不到的那些 JavaScript 事
- 运行时自省:JSX 编译后就变成函数调用,无法再像模板字符串那样在运行时解析 AST。
- 多语言语法:不能在 JSX 中直接写 Python / Rust 表达式;而 JavaScript 的 Tagged Template 可以。
- 非 React 世界:Vue、Svelte、Solid 都提供了自己的 JSX 适配层,但语义不尽相同;脱离框架使用 JSX 需要额外运行时。
五、生态:2025 年的工程化实践清单
场景 | 推荐做法 |
---|---|
新项目 | React 18 + TypeScript + Vite + SWC(或 Babel)启用 react-jsx 转换 |
旧项目迁移 | 用 npx react-codemod update-react-imports 一键移除冗余 import React |
服务端渲染 (Next.js) | Next.js 14 已默认开启新 JSX Transform,无需额外配置 |
非 React JSX(如 Solid) | 在 vite.config.ts 里替换 @vitejs/plugin-solid ,对应 JSX factory 指向 solid-js/h |
Lint & 格式化 | ESLint @eslint/jsx + Prettier 3,确保 .jsx/.tsx 文件规则一致 |
JSX 是 JavaScript 的语法扩展,它通过编译期转换,把类 HTML 的声明式结构映射到 JavaScript 函数调用,从而让 React(或其他库)在运行时可以高效地构建、更新用户界面。 因此,学习 JSX 从来不意味着在“多学一门语言”,而是掌握一种更贴合 UI 思维的 JavaScript 写法。