React16与React17+的JSX转换差异

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. 完整迁移检查清单

  1. ✅ 升级 React 到 16.14+ 或 17+
  2. ✅ 更新 TypeScript 配置:jsx: "react-jsx"
  3. ✅ 更新 Babel 配置:runtime: "automatic"
  4. ✅ 移除不必要的 React 导入
  5. ✅ 更新 ESLint 规则
  6. ✅ 测试所有组件功能正常

总结

React 17 的新 JSX 转换带来了以下好处:

  1. 开发体验改善:不再需要手动导入 React
  2. 包体积减小:自动导入更高效
  3. 性能提升 :新的 jsxjsxs 函数
  4. 未来兼容:为 React 后续版本打下基础

这个变化是 React 生态系统的重要演进,使开发更加简洁,同时为后续的优化铺平了道路。

相关推荐
掘金安东尼5 小时前
纯 CSS 实现弹性文字效果
前端·css
牛奶6 小时前
Vue 基础理论 & API 使用
前端·vue.js·面试
牛奶6 小时前
Vue 底层原理 & 新特性
前端·vue.js·面试
anOnion6 小时前
构建无障碍组件之Radio group pattern
前端·html·交互设计
pe7er6 小时前
状态提升:前端开发中的状态管理的设计思想
前端·vue.js·react.js
SoaringHeart7 小时前
Flutter调试组件:打印任意组件尺寸位置信息 NRenderBox
前端·flutter
晚风予星8 小时前
Ant Design Token Lens 迎来了全面升级!支持在 .tsx 或 .ts 文件中直接使用 Design Token
前端·react.js·visual studio code
sunny_8 小时前
⚡️ vite-plugin-oxc:从 Babel 到 Oxc,我为 Vite 写了一个高性能编译插件
前端·webpack·架构
GIS之路8 小时前
ArcPy 开发环境搭建
前端
林小帅10 小时前
【笔记】OpenClaw 架构浅析
前端·agent