
它们做的是同一件事
把 JSX 与 TSX 转成浏览器能跑的 JavaScript。浏览器不认识尖括号,所以这一步绕不过去(Writing Markup with JSX)。
严格说,两者叫转译器(transpiler)而不是编译器:输入是高级源码,输出是同一层级的另一份高级源码,只做语法降级与语法翻译,不生成机器码。各自的官方文档里有完整的转换、插件与配置清单。
两种 JSX 转换
官方在 2020 年改写过一次 JSX 转换,于是有了新旧两套(Introducing the New JSX Transform,该页所在站点已归档)。
旧转换把 JSX 变成 React.createElement(...) 调用。因为是 React. 开头,用 JSX 的文件必须有 React 在作用域里,这就是老代码里那句 import React from 'react' 的来历。
新转换不再调用 React.createElement,而是让编译器自动插入一行 import { jsx as _jsx } from 'react/jsx-runtime',再调用 _jsx('h1', { children: '你好' })。好处是写 JSX 不必再 import React。注意用 Hook 时仍然要从 react 里 import 具体函数,省掉的只是那个默认导入。
开关是配置里的 runtime:
classic走旧转换。automatic走新转换。Babel 8 起成为默认值,TypeScript 从 4.1 起支持。
新转换带来两个连锁反应:import React from 'react' 变成多余,可以用官方 codemod 批量清掉;ESLint 的 react/react-in-jsx-scope 与 react/jsx-uses-react 两条规则也随之可以关闭。
到 React 19,新转换成为必需项,旧转换不再支持(React 19 升级指南)。
同一段 JSX 的两种产物
源码:
function App() { return <h1>你好</h1>}classic 转换后:
import React from 'react'
function App() { return React.createElement('h1', null, '你好')}automatic 转换后:
// 这一行由编译器插入,不要自己写import { jsx as _jsx } from 'react/jsx-runtime'
function App() { return _jsx('h1', { children: '你好' })}react/jsx-runtime 这两个入口只给编译器用。需要手动创建元素时,还是用 React.createElement,它没有下线。
Babel 与 SWC 的差别
Babel 用 JavaScript 写,插件生态最大,新语法通常先在它这里落地。它管的事不止 JSX:语法转换、polyfill、源码转换、自定义插件都在范围内。
SWC 用 Rust 写,编译成机器码后执行,没有 JavaScript 解释执行与 JIT 的运行时开销,所以同样的转译任务通常更快。快多少取决于代码与配置,官方有自己的基准页,需要数字时看那里,别背二手倍数。
今天的脚手架通常把这一步藏起来。你会直接碰它,一般只有两种情况:要改 runtime,或者要加一个语法插件。
自己跑一次 SWC
import swc from '@swc/core'
const { code } = swc.transformFileSync('./test.jsx', { jsc: { target: 'es5', parser: { syntax: 'ecmascript', jsx: true }, transform: { react: { runtime: 'automatic' } }, },})console.log(code)输入 test.jsx:
import { createRoot } from 'react-dom/client'
const App = () => <div>Hello SWC</div>
createRoot(document.getElementById('root')).render(<App />)输出片断:
import { jsx as _jsx } from 'react/jsx-runtime'import { createRoot } from 'react-dom/client'
var App = function () { return /*#__PURE__*/ _jsx('div', { children: 'Hello SWC' })}createRoot(document.getElementById('root')).render(/*#__PURE__*/ _jsx(App, {}))把 runtime 改成 classic,输出里的 _jsx 就换成 React.createElement,并且要求 React 在作用域里。这一行配置的差别,就是老教程里那句 import React 的存废。
转译器都在做这三步
解析:把源码字符串读成抽象语法树(AST)。JSX 标签在树里是专门的一类节点,不是字符串。
转换:遍历 AST,遇到 JSX 节点就替换成函数调用节点。runtime 决定替换成 React.createElement 还是 _jsx。
生成:把改完的 AST 写回 JavaScript 代码字符串。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 192 天
部分内容可能已过时