在react中,TSX是如何转变成JS的

TSX 到 JavaScript 的完整旅程

对于很多开发人员来说,可能仅仅学习了框架的各种方法的使用,但是并没有关心代码的流转。

比如说React中的TSX/JSX文件是怎么转变成浏览器认识的JS文件的,VUE里面的vue文件是如何转变的。

在这里,介绍下TSX文件的流转。至于vue文件,这个后续再谈论。

只记一句话:TSX 不是浏览器能直接执行的语法,必须经过编译和构建。

先从一个直觉问题开始

你写的是:

tsx 复制代码
const Button = ({ label }: { label: string }) => <button>{label}</button>;

浏览器最终执行的是:

js 复制代码
const Button = ({ label }) => React.createElement('button', null, label);

或者在新 JSX Transform 下:

js 复制代码
import { jsx as _jsx } from 'react/jsx-runtime';
const Button = ({ label }) => _jsx('button', { children: label });

核心结论:TSX -> JS 不是一步魔法,而是由 TypeScript、Babel、Bundler 共同完成的一条流水线。

项目内的主要文件

text 复制代码
src/App.tsx           TSX + React 常见写法
src/ES6Features.tsx   class/箭头函数/可选链/空值合并 等 ES6+ 语法

主要命令在 package.json

json 复制代码
"step1:tsx-to-jsx": "tsc --jsx preserve",
"step2:jsx-to-js": "babel dist-step1 --out-dir dist-step2 --extensions '.jsx'",
"step3:direct-compile": "tsc --project tsconfig.step3.json",
"step3:new-jsx": "tsc --project tsconfig.new-jsx.json",
"step4:es6-to-es5": "tsc src/ES6Features.tsx ... && babel dist-es6 --out-dir dist-es5 --config-file ./.babelrc.es5"

第一层:TSX 先去类型,再决定是否保留 JSX

Step 1: npm run step1:tsx-to-jsx

作用:

  1. 移除 TypeScript 类型
  2. 保留 JSX(输出 .jsx
  3. 生成声明文件 .d.ts

查看产物:

bash 复制代码
cat dist-step1/App.jsx

能明确看到"类型没了,但 JSX 还在"。

第二层:把 JSX 真正变成函数调用

Step 2: npm run step2:jsx-to-js

作用:Babel 读取上一步的 JSX,转换为可执行 JS。

查看产物:

bash 复制代码
cat dist-step2/App.js

你会看到典型的 _jsx / _jsxs(来自 react/jsx-runtime)。

第三层:TypeScript 也可以一步到位做 JSX 转换

传统模式(React.createElement)

命令:

bash 复制代码
npm run step3:direct-compile

配置在 tsconfig.step3.json

json 复制代码
"jsx": "react"

查看:

bash 复制代码
cat dist-step3/App.js

新模式(React 17+ JSX Transform)

命令:

bash 复制代码
npm run step3:new-jsx

配置在 tsconfig.new-jsx.json

json 复制代码
"jsx": "react-jsx"

查看:

bash 复制代码
cat dist-new-jsx/App.js
  1. 在 React 17 后出现了jsx这个方法,但是 React.createElement 也得到了保留
  2. 两种输出都可以,取决于你的编译配置
  3. react-jsx 通常更现代,也不用在每个文件手动 import React

第四层:为什么还要处理 class、箭头函数、可选链

上面步骤主要解决的是 TypeScript 和 JSX。问题是,ES6+ 语法在老环境不一定可运行。

例如这些语法:

  1. class
  2. 箭头函数 () => {}
  3. 可选链 a?.b?.c
  4. 空值合并 x ?? y
  5. async/await

这时候要用 @babel/preset-env 做"按目标环境降级"。

Step 4: npm run step4:es6-to-es5

它做两件事:

  1. 先用 tscES6Features.tsx 变成 dist-es6/ES6Features.js(仍保留较新语法)
  2. 再用 Babel + @babel/preset-env 变成 dist-es5/ES6Features.js

命令:

bash 复制代码
cat dist-es6/ES6Features.js
cat dist-es5/ES6Features.js
dist-es6/ES6Features.js 中间产物说明

这个中间产物是 TypeScript 编译的直接输出,保留了以下 ES6+ 语法特性:

  • class 语法(ES6)
  • ✅ 箭头函数 () => {}(ES6)
  • async/await 语法(ES2017)
  • ✅ 可选链 ?.(ES2020)
  • ✅ 空值合并 ??(ES2020)
  • const/let 声明(ES6)
  • ✅ 模板字符串(ES6)
  • ✅ 解构赋值(ES6)
  • ✅ 展开运算符(ES6)
  • ✅ Promise(ES6)

这一步只负责去掉 TypeScript 类型,不做进一步的语法降级。假如你直接在现代浏览器(Node 14+)运行它,能正常执行。

dist-es5/ES6Features.js 最终产物说明

这个文件经过 Babel 转换,所有 ES6+ 语法都被改写成 ES5:

你会直观看到:

  1. class 被改写成函数和辅助方法
  2. 箭头函数被改写为普通函数
  3. 可选链被展开为兼容写法
  4. async/await 被改写为 Promise 链或生成器

这样做的代价是代码体积增大(需要更多 helper 函数),但优势是兼容老浏览器(IE11、旧版 Android 等)。

如何选择
  • 现代项目 :用 dist-es6 即可(size 小、加载快)
  • 需要兼容老环境 :用 dist-es5(多了 polyfill 和 helper,但兼容性好)

打包阶段:Webpack

命令:

bash 复制代码
npm run build:webpack

输出:

bash 复制代码
ls -la dist-webpack/

Webpack 负责把模块组织成浏览器可加载的 bundle,并串联 loader。语法转换能力来自 ts-loader / babel-loader 和它们的配置。

流转过程

text 复制代码
TSX 源码
  -> TypeScript: 去类型,转换成 JSX
  -> Babel: 按目标环境降级 ES6+ 语法(可选)
  -> Webpack: 打包模块、输出 bundle

项目地址

相关推荐
葡萄城技术团队1 小时前
【实践篇】从零到一:手把手教你搭建一套企业级 SpreadJS 协同设计器
前端
进击的尘埃2 小时前
SOLID 原则在 React 组件库里怎么落地:五个重构案例
javascript
忆江南2 小时前
# iOS Block 深度解析
前端
米丘2 小时前
vue-router v5.x 路由模式关于 createWebHistory、 createWebHashHistory的实现
前端
本末倒置1832 小时前
Bun 内置模块全解析:告别第三方依赖,提升开发效率
前端·javascript·node.js
踩着两条虫2 小时前
AI 驱动的 Vue3 应用开发平台 深入探究(二):核心概念之DSL模式与数据模型
前端·vue.js·ai编程
进击的尘埃2 小时前
中介者模式:把面板之间的蜘蛛网拆干净
javascript
牛奶2 小时前
200 OK不是"成功"?HTTP状态码潜规则
前端·http·浏览器
Hilaku3 小时前
OpenClaw 很爆火,但没人敢聊它的权限安全🤷‍♂️
前端·javascript·程序员