微前端生命周期解析
微前端架构通过解耦单体应用为多个独立子应用,实现了技术栈无关性、独立部署与团队自治等核心价值。然而,其核心挑战在于如何协调主应用与子应用的生命周期,确保资源加载、环境隔离与状态管理的无缝衔接。
一、主应用生命周期
主应用作为微前端架构的核心调度者,负责全局配置、子应用注册与资源调度,其生命周期包含以下关键阶段:
1. 全局配置初始化(start)
qiankun 通过 start(opts)
方法初始化全局参数,包括:
- 预加载策略(prefetch):根据
prefetch
参数决定是否在首个子应用挂载后预加载其他子应用资源,通过监听single-spa:first-mount
事件触发。 - 沙箱模式(sandbox):通过
sandbox
参数启用 JS 沙箱环境,支持LegacySandbox
(单实例)和ProxySandbox
(多实例),并根据浏览器兼容性自动降级为SnapshotSandbox
。 - 单实例模式(singular):
singular
参数控制是否允许同时运行多个子应用,默认单实例模式下,新子应用挂载前会卸载旧应用。
start({
// 预加载策略
prefetch: 'all',
sandbox: {
// CSS严格隔离(Shadow DOM)
strictStyleIsolation: true,
// 实验性样式隔离(CSS前缀注入)
experimentalStyleIsolation: true
},
// 单实例模式
singular: true
})
2. 子应用注册(registerMicroApps)
主应用通过 registerMicroApps
注册子应用,核心配置包括:
- 路由匹配规则(activeRule):基于
activeRule
定义子应用激活的 URL 路径,如genActiveRule('/app1')
匹配/app1/**
。 - 生命周期钩子注入:定义全局钩子(
beforeLoad
、beforeMount
、afterUnmount
),用于统一处理权限校验、埋点上报等横切关注点。 - 资源加载入口:通过
entry
指定子应用的 HTML 入口地址,qiankun 使用import-html-entry
库解析 HTML,提取 JS/CSS 资源并执行脚本。
3. 完整示例
下面以 React 主应用为例,改造入口文件:
main-app/src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { registerMicroApps, start } from "qiankun";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
// 主应用渲染函数
function renderMainApp() {
root.render(
<React.StrictMode>
<BrowserRouter>
{/* 子应用容器需确保存在 App 中 */}
<App />
</BrowserRouter>
</React.StrictMode>
);
}
// 注册微应用配置
const microApps = [
{
name: "app1-react", // 子应用名称(需与子应用 package.json 中的 name 一致)
entry: "//localhost:7101", // 子应用入口(开发环境地址需与子应用服务端口一致)
container: "#subapp-container", // 子应用挂载容器(需要存在于主应用 DOM 中)
activeRule: "/app1-react", // 子应用路由激活规则
},
{
name: "app2-vue",
entry: "//localhost:7102",
container: "#subapp-container",
activeRule: "/app2-vue",
},
];
// 启动配置
function initQiankun() {
// 注册微应用
registerMicroApps(microApps, {
beforeLoad: [(app) => console.log("checkAuth:", app.name)],
beforeMount: [(app) => console.log("initAnalytics:", app.name)],
afterMount: [(app) => console.log("After mount:", app.name)],
beforeUnmount: [(app) => console.log("Before unmount:", app.name)],
afterUnmount: [(app) => console.log("clearCache:", app.name)],
});
// 启动 qiankun
start({
// 预加载策略
prefetch: process.env.NODE_ENV === 'production' ? 'all' : false,
sandbox: {
// CSS严格隔离(Shadow DOM)
strictStyleIsolation: true,
// 实验性样式隔离(CSS前缀注入)
experimentalStyleIsolation: true
},
// 单实例模式
singular: true
});
}
// 初始化流程
function init() {
renderMainApp(); // 先渲染主应用
initQiankun(); // 再初始化 Qiankun
}
// 启动应用
init();
二、子应用生命周期
子应用需暴露标准生命周期钩子函数,供主应用调度。qiankun 基于 single-spa 规范扩展了以下阶段:
1. Bootstrap(初始化)
子应用首次加载时执行一次,用于初始化全局状态或执行预加载逻辑。例如:
export async function bootstrap(props) {
console.log('子应用初始化', props);
}
2. Mount(挂载)
子应用激活时触发,负责渲染 DOM 并绑定事件。qiankun 在此阶段完成:
- HTML 模板挂载:将解析后的子应用 HTML 插入主应用容器(如
container.querySelector('#subapp')
)。 - 沙箱环境激活:创建或恢复 JS 沙箱(如
LegacySandbox
通过 Proxy 代理window
对象),确保全局变量隔离。 - 框架渲染触发:执行子应用的框架挂载方法(如 Vue 的
$mount()
或 React 的ReactDOM.render()
)。
3. Unmount(卸载)
子应用切换时触发,用于销毁实例、清理事件监听及还原全局状态。qiankun 在此阶段:
- 框架实例销毁:调用如
Vue.$destroy()
或ReactDOM.unmountComponentAtNode()
。 - 沙箱环境重置:清除子应用对
window
的修改,恢复主应用全局状态。
4. Update(更新,可选)
子应用状态变化时触发,用于响应主应用传递的新属性(如用户权限变更)。
5. 示例
- React 子应用改造入口文件
- Vue 子应用改造入口文件
app1-react/src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
let root = null;
// 独立运行逻辑(开发环境直接渲染)
// 通过 window.__POWERED_BY_QIANKUN__ 识别是否被 Qiankun 加载
if (process.env.NODE_ENV === "development" && !window.__POWERED_BY_QIANKUN__) {
const container = document.getElementById("root");
root = ReactDOM.createRoot(container);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
}
// 导出 Qiankun 生命周期
export async function bootstrap() {
console.log("[React] app1 bootstraped");
}
// mount() 动态挂载
// 使用主应用传入的 container 挂载子应用,避免 DOM 冲突
export async function mount(props) {
console.log("[React] mount app1", props);
const { container } = props;
// 直接使用主应用传递的 container
const dom = container ? container : document.getElementById("root");
root = ReactDOM.createRoot(dom);
root.render(
<BrowserRouter
basename={window.__POWERED_BY_QIANKUN__ ? "/app1-react" : "/"}
>
<App />
</BrowserRouter>
);
}
// unmount() 清理资源
// 确保子应用卸载时释放内存,防止内存泄漏
export async function unmount() {
console.log("[React] Unmount");
root?.unmount();
root = null;
}
// 性能监控(可选)
reportWebVitals();
app2-vue/src/main.ts
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import type { QiankunProps } from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
import router from './router'
let app: ReturnType<typeof createApp>
declare global {
interface Window {
__POWERED_BY_QIANKUN__?: boolean
__INJECTED_PUBLIC_PATH_BY_QIANKUN__?: string
}
}
// 判断运行环境,如果是非 qiankun 环境,则直接挂载
if (!window.__POWERED_BY_QIANKUN__) {
// 动态同步 qiankun 注入的路径到 Vite
import.meta.env.BASE_URL = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ || '/'
app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
}
// 作为子应用时导出生命周期
export const bootstrap = async () => {
console.log('app2-vue bootstraped')
}
export const mount = async (props: QiankunProps) => {
console.log('app2-vue mount', props)
app = createApp(App)
app.use(createPinia())
app.use(router)
// 注意这里的容器 ID 需要与主应用注册时设置的 container 匹配
app.mount(props.container?.querySelector('#subapp-container') || '#subapp-container')
}
export const unmount = async () => {
app?.unmount()
}