qiankun 微前端框架 — 核心知识点与面试题问答


一、微前端概述

1.1 什么是微前端?

微前端(Micro-Frontends)是一种类似于微服务的架构风格,将单个前端应用拆分为多个可以独立开发、独立部署的小型应用,最终组合成一个完整的前端应用。

核心思想:主应用负责路由分发、样式隔离、生命周期管理,子应用各自独立运行。

1.2 为什么需要微前端?

痛点说明
技术栈耦合老项目无法升级新框架,新项目无法替换老技术栈
团队规模大单一代码库难以支撑多个团队并行开发
部署效率低修改一行代码需要重新构建整个应用
模块间依赖复杂一个大应用拆分成多个子应用,独立部署

1.3 微前端的优缺点

优点

  • 技术栈无关,子应用可以使用 React/Vue/Angular 等不同框架
  • 独立开发、独立部署
  • 渐进式升级,老项目可逐步迁移

缺点

  • 复杂度增加,调试困难
  • 样式隔离存在边界情况
  • 子应用间通信机制复杂
  • 首屏加载可能变慢(多个 JS 加载)

二、qiankun 核心概念

2.1 qiankun 是什么?

qiankun 是蚂蚁金服开源的微前端框架,基于 single-spa 改造,是目前国内最流行的微前端方案之一。

核心特性

  • 基于 single-spa 封装,使用更简单
  • 技术栈无关,子应用无感知
  • 样式隔离(CSS Scoped)
  • JS 沙箱(ProxySandbox / LegacySandbox)
  • 应用间通信
  • 预加载策略

2.2 架构组成

+--------------------------------------------------+
|                  主应用 (Host)                    |
|  +-------------+  +-------------+  +-------------+|
|  | 子应用A     |  | 子应用B     |  | 子应用C     ||
|  | React      |  | Vue         |  | Angular     ||
|  +-------------+  +-------------+  +-------------+|
|                                                   |
|  路由分发 | 沙箱隔离 | 样式隔离 | 通信机制         |
+--------------------------------------------------+

三、主应用开发

3.1 注册微应用

import { registerMicroApps, start } from 'qiankun';

// 注册子应用
registerMicroApps([
  {
    name: 'react-app',        // 应用名,唯一标识
    entry: '//localhost:3001', // 子应用入口地址(HTML 或 JS URL)
    container: '#container',   // 挂载容器
    activeRule: '/react',      // 激活路由规则
    props: {                   // 向子应用传递参数
      message: 'hello'
    }
  },
  {
    name: 'vue-app',
    entry: '//localhost:3002',
    container: '#container',
    activeRule: '/vue'
  }
]);

// 启动 qiankun
start({ sandbox: { strictStyleIsolation: true } });

关键参数说明

参数说明必填
name应用唯一名称
entry子应用入口,可以是 URL 或 HTML 字符串
container子应用挂载的 DOM 容器选择器
activeRule路由激活规则,决定何时加载子应用
props主应用向子应用传递的数据
sandbox沙箱配置(单个应用的沙箱覆盖全局配置)
data进入子应用时携带的数据
boundary错误边界容器选择器

3.2 启动配置

start({
  sandbox: {
    strictStyleIsolation: false,    // 严格样式隔离(使用 shadow DOM)
    experimentalStyleIsolation: false, // 实验性样式隔离
    strictStyleIsolation: false     // Proxy 沙箱
  },
  prefetch: true,                   // 是否开启预加载
  prefetchBlock: false,             // 是否预加载子应用的 js/css 入口
  singular: true,                   // 是否单实例模式
  fetch,                            // 自定义 fetch
  getPublicPath,                    // 自定义子应用入口地址
  beforeLoad,                       // 加载前钩子
  beforeMount,                      // 挂载前钩子
  afterUnmount                      // 卸载后钩子
});

3.3 生命周期钩子

import { registerMicroApps, start, addErrorHandler } from 'qiankun';

registerMicroApps([
  {
    name: 'react-app',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/react',
    // 生命周期钩子
    beforeLoad: (app) => console.log('before load', app.name),
    beforeMount: [(app) => console.log('before mount', app.name)],
    afterUnmount: [(app) => console.log('after unmount', app.name)],
  }
]);

// 全局错误处理
addErrorHandler((e) => {
  console.error('qiankun error:', e);
});

生命周期说明

钩子时机用途
beforeLoad子应用加载前添加统计、日志、动态注入 script
beforeMount子应用挂载前准备数据、修改 DOM
afterUnmount子应用卸载后清理工作

3.4 路由集成

主应用使用 history 模式

import { createHashHistory } from 'history';

// 主应用路由配置
const history = createHashHistory();

registerMicroApps([
  {
    name: 'react-app',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/react',
  }
]);

start({ history }); // 将主应用的路由 history 传给 qiankun

子应用路由配置(子应用也需要使用 history 模式,但路由前缀由 activeRule 控制):

// React 子应用
import { BrowserRouter as Router } from 'react-router-dom';

function App() {
  return (
    <Router basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Router>
  );
}

四、子应用开发

4.1 导出生命周期

子应用必须导出 bootstrapmountunmount 三个生命周期函数:

// src/qiankun.js
import ReactDOM from 'react-dom';
import App from './App';

let instance = null;

export async function bootstrap() {
  console.log('[react] react bootstrap');
}

export async function mount(props) {
  console.log('[react] react mount', props);
  instance = ReactDOM.createRoot(document.getElementById('root'));
  instance.render(<App />);
}

export async function unmount() {
  console.log('[react] react unmount');
  instance.unmount();
  instance = null;
}

// 开发环境需要暴露给 qiankun
if (window.__POWERED_BY_QIANKUN__) {
  // 生产环境
}

4.2 生命周期详解

生命周期调用时机说明
bootstrap应用初始化时,只调用一次初始化全局资源
mount应用每次挂载时调用渲染应用内容
unmount应用每次卸载时调用清理资源

注意

  • bootstrap 只在第一次加载时调用
  • mountunmount 会在每次切换时调用
  • 子应用需要做好状态清理,避免内存泄漏

4.3 配置 Webpack / Vite

Webpack 配置

// webpack.config.js
const { getPublicPath } = require('qiankun');

module.exports = {
  output: {
    // 微服务部署需要设置 publicPath
    publicPath: `//localhost:${port}`,
    library: 'reactApp',           // 暴露的全局变量名
    libraryTarget: 'umd',          // 打包格式
    jsonpFunction: `webpackJsonp_${'reactApp'}`, // 避免冲突
  }
};

Vite 配置

// vite.config.js
export default defineConfig({
  build: {
    cssCodeSplit: false,
    lib: {
      entry: 'src/main.js',
      formats: ['umd'],
      fileName: 'index',
      name: 'vueApp',
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
});

4.4 资源路径处理

// 子应用中获取 qiankun 传递的 publicPath
import { getPublicPath } from 'qiankun';

async function setup() {
  const publicPath = await getPublicPath();
  console.log(publicPath);
}

五、样式隔离

5.1 CSS Scoped(默认)

qiankun 默认使用 CSS Scoped 方案,通过 JS 动态修改样式选择器实现隔离。

// 主应用配置
start({
  sandbox: {
    experimentalStyleIsolation: true, // 开启实验性样式隔离
  }
});

原理:给子应用的 CSS 选择器添加作用域后缀,如 .app-wrapper[data-qiankun-react-app]

5.2 Shadow DOM(严格样式隔离)

start({
  sandbox: {
    strictStyleIsolation: true, // 使用 Shadow DOM
  }
});

优点:完全隔离,100% 样式独立。

缺点

  • 弹窗组件(Modal/Popover)会渲染在 Shadow DOM 内,无法显示在主应用区域
  • 部分浏览器兼容性
  • 性能开销较大

5.3 样式隔离原理

默认方案(CSS Scoped):
  1. 记录子应用原始样式
  2. 解析 CSSOM,修改选择器添加作用域后缀
  3. 重新插入修改后的样式

Shadow DOM 方案:
  1. 使用 Shadow Root 创建隔离 DOM 树
  2. 子应用挂载到 Shadow Root
  3. 样式天然隔离

六、JS 沙箱

6.1 为什么需要沙箱?

微前端环境下,多个子应用共享同一个浏览器全局上下文,容易出现:

  • 全局变量污染
  • 事件监听器冲突
  • window 属性修改导致的应用间干扰

6.2 ProxySandbox(双例模式,默认)

start({
  sandbox: {
    proxySandboxType: 'proxy', // 默认
  }
});

原理

  • 使用 Proxy 包装 window
  • 每个子应用拥有独立的代理 window
  • 子应用对 window 的读写操作在代理层拦截
  • 子应用卸载后恢复 window 状态

双例模式

  • 在沙箱环境中创建全新的 DOM 渲染树
  • 子应用以为自己在操作真实的 window
  • 非沙箱环境(如独立访问)使用真实 window

6.3 LegacySandbox(单例模式)

start({
  sandbox: {
    proxySandboxType: 'legacy',
  }
});

原理

  • 所有子应用共享同一个 window
  • 通过记录 + 恢复 window 属性实现隔离
  • 性能更好,但隔离性较弱

6.4 沙箱对比

特性ProxySandboxLegacySandbox
隔离性
性能略低略高
兼容性需要 ES6 Proxy兼容 IE
适用场景大多数场景需要兼容 IE

6.5 关闭沙箱

start({ sandbox: false });

不推荐关闭,仅在特殊场景(如子应用依赖全局变量且无法改造)下使用。


七、应用间通信

7.1 props 传递(主 -> 子)

// 主应用
registerMicroApps([
  {
    name: 'react-app',
    entry: '//localhost:3001',
    container: '#container',
    activeRule: '/react',
    props: {
      userInfo: { name: '张三', role: 'admin' },
      onEvent: (data) => console.log('收到子应用事件', data)
    }
  }
]);

// 子应用
export function mount(props) {
  console.log(props.userInfo);    // { name: '张三', role: 'admin' }
  console.log(props.onEvent);     // 函数引用
  props.onEvent({ type: 'click' }); // 调用主应用方法
}

7.2 microApp.emit / globalState(推荐)

import { microApp } from 'qiankun';

// 子应用 A 发送
microApp.emit('custom-event', { data: 'hello' });

// 子应用 B 接收
microApp.on('custom-event', (data) => {
  console.log(data); // { data: 'hello' }
});

7.3 globalState(状态管理)

// 主应用
import { initGlobalState, MicroApp } from 'qiankun';

const initialState = { user: null };
const actions = initGlobalState(initialState);

// 监听状态变化
actions.subscribe((state, prevState) => {
  console.log('state changed:', state, prevState);
});

// 设置新状态
actions.setState({ user: { name: '张三' } });

// 子应用获取
import { microApp } from 'qiankun';
microApp.getState(); // 获取全局状态
microApp.setState({ user: { name: '李四' } }); // 修改全局状态

7.4 其他通信方式

方式说明适用场景
URL 参数通过路由参数传递简单数据传递
localStorage本地存储持久化数据共享
window 事件自定义事件简单场景
iframe postMessage跨域通信跨域场景

八、预加载策略

8.1 为什么需要预加载?

子应用切换时需要重新加载 JS/CSS,造成白屏。预加载可以在空闲时提前加载资源,提升切换速度。

8.2 预加载配置

start({
  prefetch: 'all',              // 所有子应用预加载
  // 或 true(默认,路由匹配到前预加载)
  // 或 false(关闭预加载)

  prefetchBlock: false,         // 是否预加载子应用的 jsEntry/cssEntry
  beforeLoad: async (app) => {
    // 自定义预加载逻辑
  }
});

8.3 预加载时机

应用注册 -> 路由匹配前 -> 预加载资源 -> 加载子应用
                    ^
              空闲时执行(requestIdleCallback)

九、单实例与多实例

9.1 单实例模式(默认)

start({ singular: true });

同一时间只有一个子应用处于活跃状态,卸载非活跃应用后再挂载新应用。

9.2 多实例模式

start({ singular: false });

多个子应用可以同时活跃,适用于需要同时展示多个子应用的场景。


十、进阶特性

10.1 动态微应用

import { loadMicroApp } from 'qiankun';

// 动态加载
const subApp = loadMicroApp({
  name: 'dynamic-app',
  entry: '//localhost:3003',
  container: '#dynamic-container',
});

// 动态卸载
subApp.unmount();

10.2 子应用预加载

// 在子应用中导出 preload 生命周期
export async function preload() {
  // 预加载逻辑,如预请求 API、预渲染
}

10.3 主应用生命周期

主应用也可以导出生命周期(较少用):

// 主应用也可以作为子应用被更上层的壳加载
export async function bootstrap() {}
export async function mount() {}
export async function unmount() {}

10.4 路由同步

// 主应用监听子应用路由变化
import { initGlobalState } from 'qiankun';

const actions = initGlobalState({ pathname: '' });

// 子应用路由变化时通知主应用
window.addEventListener('qiankun-navigation', (e) => {
  actions.setState({ pathname: e.detail.pathname });
});

十一、常见问题与解决方案

11.1 子应用资源 404

原因:publicPath 配置不正确。

解决

// webpack
output: {
  publicPath: `//localhost:${port}`,
}

// vite
base: `//localhost:${port}`,

11.2 样式穿透问题

/* 深度选择器 */
:deep(.some-class) {
  color: red;
}

/* 或使用 /deep/ */
/deep/ .some-class {
  color: red;
}

11.3 弹窗/下拉框被遮挡

使用 strictStyleIsolation 时,弹窗渲染在 Shadow DOM 内。

解决

  • 使用 getContainer 配置指定弹窗挂载位置
  • 或关闭严格样式隔离

11.4 内存泄漏

常见原因

  • 未清理事件监听器
  • 未卸载定时器
  • 未销毁组件实例

解决

export async function unmount() {
  // 清理定时器
  clearInterval(timer);
  // 清理事件
  window.removeEventListener('resize', handler);
  // 销毁组件
  instance.unmount();
}

11.5 子应用独立访问

// 子应用入口
if (!window.__POWERED_BY_QIANKUN__) {
  // 独立运行
  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
}

十二、面试常见问题

Q1: 微前端和单体应用相比有什么优势?

  • 技术栈无关:不同子应用可以使用不同的框架,渐进式升级
  • 独立部署:子应用可以独立构建部署,不依赖主应用
  • 团队自治:不同团队可以独立开发自己的子应用
  • 按需加载:只加载当前需要的子应用资源
  • 故障隔离:一个子应用崩溃不影响其他子应用

Q2: qiankun 的样式隔离原理是什么?


qiankun 默认使用 CSS Scoped 方案,原理是:

  1. 子应用加载时,qiankun 会拦截子应用注入的样式标签
  2. 解析 CSSOM,获取所有选择器
  3. 为每个选择器添加作用域后缀(如 [data-qiankun-app-name]
  4. 重新插入修改后的样式
  5. 子应用的 DOM 节点也会添加对应的属性选择器

也可以开启 strictStyleIsolation 使用 Shadow DOM,通过 Shadow Root 实现完全隔离。

Q3: qiankun 的 JS 沙箱有几种模式?有什么区别?


两种模式:

  1. ProxySandbox(双例模式)

    • 使用 Proxy 代理 window
    • 每个子应用有独立的代理 window
    • 子应用卸载后恢复 window 状态
    • 隔离性强,但需要 ES6 Proxy 支持
  2. LegacySandbox(单例模式)

    • 所有子应用共享 window
    • 通过记录 + 恢复 window 属性实现隔离
    • 兼容性好,支持 IE
    • 隔离性较弱,可能出现变量污染

默认使用 ProxySandbox。

Q4: 子应用的生命周期有哪些?分别在什么时候调用?


三个生命周期:

  1. bootstrap:应用初始化时调用,只调用一次,用于初始化全局资源
  2. mount:应用每次挂载时调用,用于渲染应用内容
  3. unmount:应用每次卸载时调用,用于清理资源

注意:bootstrap 只在首次加载时调用,mountunmount 在每次切换时都会调用。

Q5: 主应用和子应用如何通信?


几种方式:

  1. props 传递:主应用通过 registerMicroAppsprops 参数向子应用传递数据
  2. microApp.emit/on:子应用之间通过 microApp.emit 发送事件,microApp.on 接收
  3. globalState:通过 initGlobalState 创建全局状态,子应用通过 microApp.getState/setState 读写
  4. URL 参数:通过路由参数传递简单数据
  5. localStorage:通过本地存储共享数据

推荐方式:简单数据用 props,复杂场景用 globalState。

Q6: qiankun 的路由是怎么工作的?

  • 主应用使用 history 或 hash 路由
  • 子应用的 activeRule 定义路由匹配规则
  • 当浏览器 URL 匹配到 activeRule 时,qiankun 加载并挂载对应子应用
  • 子应用的路由前缀由 activeRule 决定,子应用内部路由需要在 basename 中配置前缀
  • 主应用需要将自己的 history 实例传给 start(),qiankun 会接管路由切换

Q7: 如何处理子应用的弹窗/下拉框被遮挡问题?

  1. 如果使用了 strictStyleIsolation(Shadow DOM),弹窗会渲染在 Shadow DOM 内
  2. 解决方案:
    • 使用 getContainer 配置指定弹窗挂载到主应用的容器中
    • 关闭严格样式隔离,使用 CSS Scoped
    • 使用 Portal 将弹窗挂载到主应用的 DOM 节点

Q8: qiankun 的预加载机制是什么?

  • 预加载在子应用路由匹配到之前提前加载资源
  • 默认使用 requestIdleCallback 在空闲时加载
  • 配置 prefetch: true 开启(路由匹配前预加载)
  • 配置 prefetch: 'all' 预加载所有子应用
  • 配置 prefetchBlock: true 预加载子应用的 jsEntry/cssEntry
  • 预加载可以显著提升子应用切换速度

Q9: 子应用如何独立运行和微前端模式下运行?


通过 window.__POWERED_BY_QIANKUN__ 判断:

if (window.__POWERED_BY_QIANKUN__) {
  // 微前端模式
  export { mount, unmount };
} else {
  // 独立运行
  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
}

独立运行时,子应用需要配置正确的 publicPath 和路由 basename

Q10: qiankun 的 singular 模式是什么?

  • singular: true(默认):单实例模式,同一时间只有一个子应用活跃
  • singular: false:多实例模式,多个子应用可以同时活跃

单实例模式下,切换子应用时会先卸载旧应用再挂载新应用。多实例模式下,子应用可以同时存在,适用于需要同时展示多个子应用的场景。

Q11: 如何处理子应用之间的依赖冲突?

  1. JS 沙箱:qiankun 的 ProxySandbox 隔离了全局变量
  2. CSS 隔离:样式隔离避免冲突
  3. 资源命名:子应用的 webpack libraryjsonpFunction 使用不同名称
  4. 第三方库:主应用可以全局引入公共依赖,子应用通过 externals 排除
  5. 版本统一:尽量保持子应用的依赖版本一致

Q12: qiankun 相比其他微前端方案(如 Module Federation)有什么优劣?

qiankun 优势

  • 基于 single-spa,成熟稳定
  • 样式隔离和沙箱机制完善
  • 子应用无感知,改造成本低
  • 社区活跃,中文文档完善

Module Federation 优势

  • Webpack 5 原生支持
  • 共享依赖更灵活
  • 性能更好(不需要隔离沙箱)
  • 模块级别共享,更细粒度

选择建议

  • 老项目改造、需要样式/JS 隔离 -> qiankun
  • 新项目、Webpack 5 环境 -> Module Federation
  • 需要共享组件/模块 -> Module Federation

Q13: 如何优化微前端的首屏加载速度?

  1. 预加载:开启 prefetch 预加载子应用资源
  2. 资源压缩:Gzip/Brotli 压缩
  3. CDN 加速:静态资源使用 CDN
  4. 按需加载:子应用使用路由懒加载
  5. 公共依赖提取:主应用引入公共库,子应用 externals
  6. 预渲染:子应用首屏使用 SSR
  7. 骨架屏:加载期间显示骨架屏

Q14: qiankun 支持 IE 浏览器吗?

  • qiankun 2.x 支持 IE 11+
  • 需要使用 LegacySandbox(单例模式)
  • 需要 polyfill Promise 和 Proxy 的降级方案
  • 样式隔离使用 CSS Scoped(Shadow DOM 不支持 IE)
  • 配置:start({ sandbox: { proxySandboxType: 'legacy' } })

Q15: 如何处理子应用的错误?

  1. 全局错误监听
import { addErrorHandler } from 'qiankun';
addErrorHandler((e) => {
  console.error('qiankun error:', e);
});
  1. 子应用错误边界:使用 React Error Boundary 等机制

  2. 应用级错误边界registerMicroApps 中配置 boundary 参数

  3. 子应用内部错误处理:子应用自身做好错误边界和错误上报


十三、总结

qiankun 的核心知识点可以概括为:

模块核心要点
架构主应用注册子应用,子应用导出生命周期
路由activeRule 匹配路由,history 模式同步
沙箱ProxySandbox(双例)/ LegacySandbox(单例)
样式隔离CSS Scoped / Shadow DOM
通信props / microApp.emit / globalState
生命周期bootstrap / mount / unmount
预加载prefetch 提升切换速度
部署子应用需要配置 library / publicPath
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值