在微前端架构从“跑通”走向“生产级”的过程中,单纯的嵌入只是第一步。要构建一个健壮的系统,我们必须解决通信闭环、状态保持、路由接管、资源路径这四个核心问题。
以下是针对 React 场景的深度实践技巧和完整代码配置。
一、 全局数据通信(Data Communication)
这是最核心的功能。Micro-app 提供了一套基于发布订阅模式的数据通道。
1. 核心逻辑
-
下发(Host -> Child): 主应用通过
setData广播数据,子应用监听数据变化。 -
反向(Child -> Host): 子应用通过
dispatch发送数据,主应用监听。 -
注意点: 必须在组件卸载时解绑监听器,否则会导致内存泄漏。
2. 代码实现
主应用 (Main App) - 控制中心
TypeScript
// main-app/src/App.tsx
import { useState } from 'react';
import microApp from '@micro-zoe/micro-app';
const App = () => {
const [childMsg, setChildMsg] = useState('');
// 1. 向子应用发送数据
const sendToChild = () => {
// setData(子应用名称, 数据对象)
microApp.setData('child-app', {
token: 'a1b2c3d4',
theme: 'dark'
});
};
// 2. 接收子应用数据
// 注意:这个函数最好用 useCallback 包裹或者放在 useEffect 里绑定
const handleDataChange = (e: CustomEvent) => {
console.log('收到子应用数据:', e.detail.data);
setChildMsg(e.detail.data.message);
};
return (
<div>
<button onClick={sendToChild}>下发 Token 给子应用</button>
<p>来自子应用的消息: {childMsg}</p>
<micro-app
name="child-app"
url="http://localhost:4000/"
iframe
// 绑定数据监听事件 (onDataChange)
onDataChange={handleDataChange}
></micro-app>
</div>
);
};
子应用 (Child App) - 响应端
为了让 TS 不报错,先在子应用 src/vite-env.d.ts 补充类型:
TypeScript
interface Window {
microApp: any;
__MICRO_APP_ENVIRONMENT__: boolean;
}
然后是业务代码:
TypeScript
// child-app/src/App.tsx
import { useEffect, useState } from 'react';
const App = () => {
const [info, setInfo] = useState({ token: '', theme: '' });
useEffect(() => {
// 检查是否在微前端环境中
if (window.microApp) {
// 1. 绑定监听函数
const dataListener = (data: any) => {
console.log('主应用发来数据:', data);
setInfo(data);
};
// 开启监听,第二个参数 true 代表“一启动就立即获取一次当前缓存数据”
window.microApp.addDataListener(dataListener, true);
// 2. 清理函数 (非常重要!)
return () => {
window.microApp.removeDataListener(dataListener);
};
}
}, []);
// 3. 反向发送数据
const talkToMain = () => {
window.microApp?.dispatch({ message: '任务已完成', status: 200 });
};
return (
<div>
<h3>子应用面板</h3>
<p>接收到的 Token: {info.token}</p>
<button onClick={talkToMain}>汇报给主应用</button>
</div>
);
};
二、 路由接管与 BaseRoute
如果子应用本身有多页路由(如使用了 react-router-dom),你需要告诉子应用:“你的根路径不是 /,而是主应用分配给你的 /child-one”。
1. 主应用配置
假设我们要把子应用挂载在 http://localhost:3000/child-one/* 下。
TypeScript
// main-app
<micro-app
name="child-app"
url="http://localhost:4000/"
baseroute="/child-one" // 告诉子应用,你的地盘从这里开始
></micro-app>
2. 子应用配置
子应用的路由必须识别这个 basename。
TypeScript
// child-app/src/main.tsx
import { BrowserRouter } from 'react-router-dom';
// 动态获取:如果是独立运行,base就是 '/';如果是微前端,就是 '/child-one'
const BASE_ROUTE = window.__MICRO_APP_BASE_ROUTE__ || '/';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter basename={BASE_ROUTE}>
<App />
</BrowserRouter>
);
三、 性能优化:Keep-alive (保活)
默认情况下,当你从子应用 Tab A 切到 Tab B,子应用会被销毁。再次切回来时需要重新加载资源、重新渲染,体验很差。
keep-alive 属性可以让子应用在切走时只是“隐藏”而不是“销毁”。
用法非常简单:
TypeScript
<micro-app
name="child-app"
url="..."
keep-alive // 开启保活
></micro-app>
深度解读:
开启后,子应用的 componentWillUnmount (或者 useEffect 的 cleanup) 不会执行。
如果你需要感知“我被切走了”和“我又回来了”,需要使用 micro-app 提供的特定生命周期:
TypeScript
// 子应用内部
useEffect(() => {
const onMount = () => console.log('我被唤醒了 (created or activated)');
const onUnmount = () => console.log('我被隐藏了 (unmounted or deactivated)');
window.addEventListener('appstate-change', (e: any) => {
if (e.detail.appState === 'active') onMount();
if (e.detail.appState === 'deactive') onUnmount();
});
}, []);
四、 静态资源路径补全 (Public Path)
这是新手最容易遇到的坑:子应用的图片/字体在独立运行时正常,嵌入主应用后 404。
原因: 浏览器请求相对路径资源(如 /logo.png)时,会基于地址栏的域名(主应用 localhost:3000)去请求,但资源实际在子应用服务器(localhost:4000)上。
解法: 在子应用入口文件的最顶部(第一行)注入 public path。
TypeScript
// child-app/src/main.ts (或 index.ts)
// 必须放在 import App 之前
if (window.__MICRO_APP_ENVIRONMENT__) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__;
}
// Vite 项目的特殊处理:
// Vite 在开发模式下处理 public path 比较特殊,通常建议写死图片绝对路径
// 或者在 vite.config.ts 中配置 base
针对 Vite 的实用技巧:
在 vite.config.ts 中写一个简单的插件,自动转换 HTML 中的资源链接(如果 micro-app 的 iframe 模式处理不完美时):
TypeScript
// child-app/vite.config.ts
export default defineConfig({
base: process.env.NODE_ENV === 'production' ? 'http://your-cdn.com/child-app/' : '/',
// ...其他配置
})
五、 预加载 (PreFetch)
为了极致体验,你可以在用户登录主应用时,就利用浏览器的空闲时间悄悄加载子应用的资源。
在主应用的入口文件 main.tsx 中配置:
TypeScript
// main-app/src/main.tsx
import microApp from '@micro-zoe/micro-app';
microApp.start({
preFetchApps: [
{ name: 'child-app', url: 'http://localhost:4000/' }
]
});
总结清单
-
通信:优先使用
setData和addDataListener,记得解绑。 -
路由:子应用一定要配合设置
basename,值为window.__MICRO_APP_BASE_ROUTE__。 -
体验:对于复杂的子应用,务必开启
keep-alive。 -
样式:推荐使用
iframe属性开启强隔离(sandbox),这能省去90%的 CSS 冲突烦恼。 -
类型:不要在 TS 类型上纠结太久,善用
global.d.ts或window扩展来解决报错。