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

能否在React应用的虚拟DOM中嵌入另一个独立React应用?

问题原因分析与解决方案

为什么当前方式不生效?

  • 脚本执行顺序问题:你在index.html里提前引入了my-callee.js,此时CallerApp还未挂载到root节点,DOM中根本不存在id="callee"的元素,CalleeApp初始化时找不到挂载节点,自然无法完成渲染。
  • React DOM更新机制限制:即使后续CallerApp渲染出id="callee"的节点,CalleeApp的初始化代码已经执行完毕,不会自动触发重新挂载逻辑。

可行的整合方案

方案一:将CalleeApp改为可手动触发挂载的方式

  1. 修改CalleeApp的入口文件,把自动挂载逻辑改成可调用的暴露方法,不要直接执行:
// callee-entry.js(CalleeApp的入口)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

// 暴露挂载方法到全局
window.mountCalleeApp = function(container) {
  const root = ReactDOM.createRoot(container);
  root.render(<App />);
};
  1. 在CallerApp中,利用useEffect确保DOM渲染完成后,调用挂载方法:
// CallerApp.tsx
import { useEffect, useRef } from 'react';

// 声明全局方法类型
declare global {
  interface Window {
    mountCalleeApp: (container: HTMLElement) => void;
  }
}

function CallerApp() {
  const calleeRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 确保节点存在且挂载方法可用时执行
    if (calleeRef.current && window.mountCalleeApp) {
      window.mountCalleeApp(calleeRef.current);
    }
  }, []);

  return (
    <div>
      <p>callee app</p>
      <div ref={calleeRef}></div>
    </div>
  );
}

export default CallerApp;

方案二:动态加载CalleeApp脚本

如果不想提前在index.html引入脚本,可以在CallerApp需要时动态加载,避免执行顺序问题:

// CallerApp.tsx
import { useEffect, useRef, useState } from 'react';

function CallerApp() {
  const calleeRef = useRef<HTMLDivElement>(null);
  const [isScriptLoaded, setIsScriptLoaded] = useState(false);

  useEffect(() => {
    if (!isScriptLoaded) {
      const script = document.createElement('script');
      script.type = 'module';
      script.crossOrigin = '';
      script.src = '/assets/javascript/my-callee.js';
      script.onload = () => {
        setIsScriptLoaded(true);
        // 脚本加载完成后执行挂载
        if (calleeRef.current && window.mountCalleeApp) {
          window.mountCalleeApp(calleeRef.current);
        }
      };
      document.body.appendChild(script);
    }
  }, [isScriptLoaded]);

  return (
    <div>
      <p>callee app</p>
      <div ref={calleeRef}></div>
    </div>
  );
}

export default CallerApp;

方案三:使用微前端框架(适合长期多应用整合场景)

如果是需要长期维护的多React应用整合,推荐用成熟的微前端方案,比如Webpack的Module Federation,能直接远程引入组件,无需手动处理挂载逻辑:

  1. CalleeApp的Webpack配置(添加ModuleFederationPlugin):
// callee-webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // 其他配置...
  plugins: [
    new ModuleFederationPlugin({
      name: 'CalleeApp',
      filename: 'remoteEntry.js',
      exposes: {
        './App': './src/App', // 暴露需要被调用的组件
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } }, // 共享React依赖,避免重复打包
    }),
  ],
};
  1. CallerApp的Webpack配置:
// caller-webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  // 其他配置...
  plugins: [
    new ModuleFederationPlugin({
      name: 'CallerApp',
      remotes: {
        CalleeApp: 'CalleeApp@/assets/javascript/remoteEntry.js', // 引入CalleeApp的远程入口
      },
      shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
    }),
  ],
};
  1. 在CallerApp中直接使用CalleeApp的组件:
// CallerApp.tsx
import React, { Suspense } from 'react';

// 懒加载远程组件
const CalleeApp = React.lazy(() => import('CalleeApp/App'));

function CallerApp() {
  return (
    <div>
      <p>callee app</p>
      <Suspense fallback={<div>加载中...</div>}>
        <CalleeApp />
      </Suspense>
    </div>
  );
}

export default CallerApp;

内容的提问来源于stack exchange,提问作者yayuyo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 04:55:37