React 16 与 React 17+ 的 JSX 转换差异
相比React16,React 17引入了一项重要的变化,使得 TSX/JSX 文件中不再需要显式地导入 React。让我详细解释这个变化。
1. 历史背景
React 16 及之前版本
jsx
// 必须导入 React
import React from 'react';
function MyComponent() {
return <div>Hello</div>;
}
转换后:
javascript
// Babel/TypeScript 转换结果
import React from 'react';
function MyComponent() {
return React.createElement('div', null, 'Hello');
}
React 17+ 版本
jsx
// 不需要导入 React!
function MyComponent() {
return <div>Hello</div>;
}
转换后:
javascript
// 新转换器会自动导入
import { jsx as _jsx } from 'react/jsx-runtime';
function MyComponent() {
return _jsx('div', { children: 'Hello' });
}
2. 转换机制的对比
旧转换器(React 16)
javascript
// JSX: <div className="container">Hello</div>
// 转换后:
React.createElement('div', { className: 'container' }, 'Hello');
// JSX: <MyComponent prop="value" />
// 转换后:
React.createElement(MyComponent, { prop: 'value' });
新转换器(React 17+)
javascript
// JSX: <div className="container">Hello</div>
// 转换后:
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx('div', { className: 'container', children: 'Hello' });
// JSX: <MyComponent prop="value" />
// 转换后:
import { jsx as _jsx } from 'react/jsx-runtime';
_jsx(MyComponent, { prop: 'value' });
3. 如何启用新转换
使用 TypeScript
json
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx", // React 17+ 新转换
// 或
"jsx": "react-jsxdev" // 开发模式
}
}
使用 Babel
javascript
// .babelrc
{
"presets": [
[
"@babel/preset-react",
{
"runtime": "automatic" // 自动导入
// 或 "runtime": "classic" // 传统方式
}
]
]
}
4. 新转换的优势
4.1 不再需要手动导入 React
javascript
// ❌ 以前:必须导入
import React from 'react';
// ✅ 现在:不需要导入
function App() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
4.2 更小的包体积
javascript
// 旧转换:每个文件都包含完整的 React.createElement
// 新转换:按需导入,tree-shaking 更友好
4.3 性能改进
javascript
// 新转换自动优化
const element = (
<div>
<span>Static</span>
<span>{dynamic}</span>
</div>
);
// 优化转换:
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
// 静态部分(jsxs 优化静态子元素)
const element = _jsxs('div', {
children: [
_jsx('span', { children: 'Static' }),
_jsx('span', { children: dynamic })
]
});
5. 特殊情况处理
5.1 仍然需要导入的情况
javascript
// 如果使用 React 的其他 API,仍然需要导入
import { useState, useEffect } from 'react';
// 但不需要导入 React 本身
5.2 自定义 JSX 工厂
typescript
// 可以自定义 JSX 工厂函数
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react" // 使用 emotion
}
}
6. 向后兼容性
6.1 逐步迁移
json
// package.json - 可以逐步升级
{
"dependencies": {
"react": "^16.14.0", // 16.14+ 支持新转换
"react-dom": "^16.14.0"
}
}
6.2 混合模式
javascript
// 可以同时使用新旧转换
// 旧组件
import React from 'react';
// 新组件
function NewComponent() {
// 不需要导入 React
return <div>New</div>;
}
7. 实际迁移示例
迁移前
typescript
// App.tsx (React 16)
import React from 'react';
import ReactDOM from 'react-dom';
const App: React.FC = () => {
return (
<div className="app">
<h1>Hello World</h1>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
迁移后
typescript
// App.tsx (React 17+)
import { createRoot } from 'react-dom/client';
function App() { // 不需要 React.FC
return (
<div className="app">
<h1>Hello World</h1>
</div>
);
}
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
8. 工具更新
8.1 ESLint 配置
javascript
// .eslintrc.js
module.exports = {
rules: {
'react/jsx-uses-react': 'off', // 不再需要
'react/react-in-jsx-scope': 'off', // 不再需要
}
};
8.2 自动移除导入
bash
# 使用 codemod 工具自动迁移
npx react-codemod update-react-imports
9. 新转换的内部机制
9.1 自动导入的源代码
javascript
// react/jsx-runtime.js (简化版)
export { jsx, jsxs, jsxDEV } from './src/jsx/ReactJSX';
// react/jsx-dev-runtime.js (开发版)
export { jsxDEV, jsxsDEV } from './src/jsx/ReactJSX';
9.2 实际使用的函数
javascript
// 生产环境
import { jsx, jsxs } from 'react/jsx-runtime';
// 开发环境
import { jsxDEV, jsxsDEV } from 'react/jsx-dev-runtime';
10. 常见问题解决
10.1 错误:"React is not defined"
javascript
// 解决方案1:升级 React 版本
"react": "^17.0.0"
// 解决方案2:更新 Babel 配置
// .babelrc
{
"presets": [
["@babel/preset-react", { "runtime": "automatic" }]
]
}
// 解决方案3:更新 TypeScript 配置
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx"
}
}
10.2 库开发者的注意事项
typescript
// 库应该支持两种模式
export { jsx, jsxs } from 'react/jsx-runtime';
export { jsxDEV, jsxsDEV } from 'react/jsx-dev-runtime';
11. 完整迁移检查清单
- ✅ 升级 React 到 16.14+ 或 17+
- ✅ 更新 TypeScript 配置:
jsx: "react-jsx" - ✅ 更新 Babel 配置:
runtime: "automatic" - ✅ 移除不必要的 React 导入
- ✅ 更新 ESLint 规则
- ✅ 测试所有组件功能正常
总结
React 17 的新 JSX 转换带来了以下好处:
- 开发体验改善:不再需要手动导入 React
- 包体积减小:自动导入更高效
- 性能提升 :新的
jsx和jsxs函数 - 未来兼容:为 React 后续版本打下基础
这个变化是 React 生态系统的重要演进,使开发更加简洁,同时为后续的优化铺平了道路。