在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

项目地址

相关推荐
圣光SG9 小时前
Java类与对象及面向对象基础核心详细笔记
java·前端·数据库
Jinuss9 小时前
源码分析之React中的useImperativeHandle
开发语言·前端·javascript
ZC跨境爬虫9 小时前
CSS核心知识点与定位实战全解析(结合Playwright爬虫案例)
前端·css·爬虫
Jinuss9 小时前
源码分析之React中的forwardRef解读
前端·javascript·react.js
mengsi559 小时前
Antigravity IDE 在浏览器上 verify 成功但本地 IDE 没反应 “开启Tun依然无济于事” —— 解决方案
前端·ide·chrome·antigravity
南风知我意9579 小时前
JavaScript 惰性函数深度解析:从原理到实践的极致性能优化
开发语言·javascript·性能优化
Можно9 小时前
pages.json 和 manifest.json 有什么作用?uni-app 核心配置文件详解
前端·小程序·uni-app
hzhsec9 小时前
钓鱼邮件分析与排查
服务器·前端·安全·web安全·钓鱼邮件
爱看老照片10 小时前
uniapp传递数值(数字)时需要使用v-bind的形式(加上冒号)
javascript·vue.js·uni-app