JSX 与 JavaScript 的关系:从语法糖到生态系统

Hi,我是前端人类学 (之前叫布兰妮甜)!

"JSX 既不是 HTML,也不是字符串;它只是 JavaScript 的一种更友好的写法。" ------React 官方文档

在 2025 年的前端开发语境里,JSX 几乎成了 React 的代名词。然而,很多初学者仍然把它当成"另一种语言"。本文尝试用一条清晰的脉络回答:JSX 与 JavaScript 到底是什么关系?它们如何分工、如何协同,以及在实际工程里有哪些最新实践。


文章目录

    • [一、血统:JSX 首先是一段合法的 JavaScript 程序](#一、血统:JSX 首先是一段合法的 JavaScript 程序)
      • [1.1 语法扩展,而非新语言](#1.1 语法扩展,而非新语言)
      • [1.2 文件扩展名只是"提示"](#1.2 文件扩展名只是“提示”)
    • [二、转译:从 JSX 到 JavaScript 的三次"变形"](#二、转译:从 JSX 到 JavaScript 的三次“变形”)
      • [2.1 经典转换:`React.createElement`](#2.1 经典转换:React.createElement)
      • [2.2 新 JSX Transform:React 17+ 的优化](#2.2 新 JSX Transform:React 17+ 的优化)
      • [2.3 TypeScript 中的 JSX 模式](#2.3 TypeScript 中的 JSX 模式)
    • [三、语义:JSX 让 JavaScript 获得了"声明式 UI"表达能力](#三、语义:JSX 让 JavaScript 获得了“声明式 UI”表达能力)
      • [3.1 声明式 vs 命令式](#3.1 声明式 vs 命令式)
      • [3.2 JSX 的 JavaScript 完全能力](#3.2 JSX 的 JavaScript 完全能力)
    • [四、边界:JSX 做不到的那些 JavaScript 事](#四、边界:JSX 做不到的那些 JavaScript 事)
    • [五、生态:2025 年的工程化实践清单](#五、生态: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 及以前的默认做法:

jsx 复制代码
// 源码
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 转换"

js 复制代码
// 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 通常是命令式:

js 复制代码
const h1 = document.createElement('h1');
h1.textContent = 'Hello';
document.body.appendChild(h1);

JSX 把同样的意图声明式地写进返回值:

jsx 复制代码
function Greeting() {
  return <h1>Hello</h1>;
}

React 负责把这段"描述"同步到真实 DOM。开发者不再关心如何更新 ,只关心长什么样

3.2 JSX 的 JavaScript 完全能力

由于 JSX 只是语法糖,你可以在标签属性、子元素中写任意 JavaScript:

jsx 复制代码
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 事

  1. 运行时自省:JSX 编译后就变成函数调用,无法再像模板字符串那样在运行时解析 AST。
  2. 多语言语法:不能在 JSX 中直接写 Python / Rust 表达式;而 JavaScript 的 Tagged Template 可以。
  3. 非 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 写法。