如何在打包后的React组件中使用外部React与ReactDOM脚本?
如何通过外部加载React/ReactDOM并避免打包进bundle?
我尝试从外部加载React和ReactDOM,而非将其打包进bundle中。当前遇到的问题是:当所有React调用都写在client.js文件中时可以正常工作,但导入其他文件的组件时就失效了。
现有代码
client.js
import App from './App.jsx'; let page_initialised = false; async function init() { if (!window.React || !window.ReactDOM) { return; } if (page_initialised) { return; } page_initialised = true; ReactDOM.hydrateRoot(document, <App />); } init(); document.querySelector('#react').addEventListener('load', init); document.querySelector('#react_dom').addEventListener('load', init);
我也尝试过异步导入App,但仍无效果:
const App = await import('./App.jsx'); ReactDOM.hydrateRoot(document, <App />);
App.jsx
const Root = React.lazy(() => import('./Root.js')); export default function App() { const [counter, setCounter] = React.useState(0); return ( <html> <head> <script id="react" crossOrigin="anonymous" src="https://unpkg.com/react@18.2.0/umd/react.development.js" async></script> <script id="react_dom" crossOrigin="anonymous" src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js" async></script> <script src="/client.js" async></script> </head> <body> <div id="root"> <React.Suspense fallback={<> <include src="Root_fallback.html"></include> </>}> <Root /> </React.Suspense> </div> </body> </html> ) }
报错信息与应用结构

图1:遇到的错误信息

图2:Firefox开发者工具中显示的应用结构
请问如何在组件中使用外部React并避免将其打包?
解决方法
1. 配置打包工具排除React/ReactDOM
不管用Webpack还是Vite,都需要明确告诉打包工具不要将React和ReactDOM打包进bundle,而是使用全局变量替代:
- Webpack:在
webpack.config.js中添加externals配置:
module.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM' } };
- Vite:在
vite.config.js中添加build.rollupOptions.external:
export default { build: { rollupOptions: { external: ['react', 'react-dom'] } } };
2. 修正组件的全局变量引用
组件直接使用React全局变量时,打包后的模块可能无法自动识别,有两种处理方式:
- 每个组件顶部显式声明:
const React = window.React; const ReactDOM = window.ReactDOM;
- 用Webpack的
ProvidePlugin自动注入:
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.ProvidePlugin({ React: 'react', ReactDOM: 'react-dom' }) ], externals: { react: 'React', 'react-dom': 'ReactDOM' } };
3. 调整脚本加载顺序
当前App.jsx中把React脚本放在<head>且用async,可能导致组件加载时React未就绪。建议将React/ReactDOM脚本移到<body>末尾,去掉async,确保依赖加载完成后再执行业务代码:
<!DOCTYPE html> <html> <head> <title>App</title> </head> <body> <div id="root"></div> <script id="react" crossOrigin="anonymous" src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script> <script id="react_dom" crossOrigin="anonymous" src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script> <script src="/client.js"></script> </body> </html>
同时简化client.js的初始化逻辑:
import App from './App.jsx'; function init() { ReactDOM.hydrateRoot(document.getElementById('root'), <App />); } if (window.React && window.ReactDOM) { init(); } else { window.addEventListener('load', init); }
4. 修正React组件的挂载规范
App.jsx返回整个<html>结构不符合React使用规范——React应挂载到已存在的DOM节点上,而非生成完整HTML。调整如下:
- 主HTML文件保留基础结构(如上面的示例)
App.jsx仅返回页面内容:
const Root = React.lazy(() => import('./Root.js')); export default function App() { const [counter, setCounter] = React.useState(0); return ( <div> <React.Suspense fallback={<div>Loading...</div>}> <Root /> </React.Suspense> </div> ); }
5. 异步导入组件的正确写法
使用await import时,需注意导入的是模块对象,要取出default导出:
const { default: App } = await import('./App.jsx'); ReactDOM.hydrateRoot(document.getElementById('root'), <App />);
内容的提问来源于stack exchange,提问作者n-l-i
相关产品推荐
相关产品推荐

