You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在打包后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 12:22:39