微前端架构实战:从入门到落地的完整指南

司马涵舒 框架 阅读 1,993
赞 67 收藏
二维码
手机扫码查看
反馈

微前端别乱搞,先想清楚这几点

我第一次上手微前端的时候,以为就是把几个子应用拼在一起,主应用搭个壳子就行。结果上线后三天两头出问题:样式冲突、内存泄漏、路由跳转错乱……折腾得我差点想回老家种地。后来踩了足够多的坑,才慢慢摸索出一套相对靠谱的写法。今天就聊聊我现在的做法,以及那些千万别碰的雷区。

微前端架构实战:从入门到落地的完整指南

我的写法,亲测靠谱

我现在用的是 qiankun,不是因为它最牛,而是社区生态成熟,文档虽然糙但能用。关键是我自己封装了一层加载逻辑,避免直接裸调 registerMicroApps。下面是我现在主应用里加载子应用的核心代码:

// main.js
import { registerMicroApps, start } from 'qiankun';

const apps = [
  {
    name: 'app1',
    entry: '//localhost:8081',
    container: '#subapp-viewport',
    activeRule: '/app1',
    props: {
      globalStore: window.__GLOBAL_STORE__,
      onGlobalStateChange: (state) => {
        // 主应用状态变更通知子应用
        console.log('子应用收到全局状态更新:', state);
      }
    }
  },
  {
    name: 'app2',
    entry: '//localhost:8082',
    container: '#subapp-viewport',
    activeRule: '/app2',
    props: {
      globalStore: window.__GLOBAL_STORE__,
      onGlobalStateChange: (state) => {
        console.log('子应用收到全局状态更新:', state);
      }
    }
  }
];

// 关键:加一层错误兜底
registerMicroApps(
  apps,
  {
    beforeLoad: async (app) => {
      console.log('准备加载子应用:', app.name);
    },
    afterMount: (app) => {
      console.log('子应用挂载完成:', app.name);
    },
    // 最重要:子应用加载失败时不要让整个页面白屏
    error: (error, app) => {
      console.error(子应用 ${app.name} 加载失败:, error);
      // 可以在这里展示 fallback UI
      document.getElementById('subapp-viewport').innerHTML = '<div>服务暂时不可用,请稍后再试</div>';
    }
  }
);

start({
  prefetch: true, // 提前加载资源,但注意别在低配手机上开,会卡
  sandbox: {
    strictStyleIsolation: false, // 别开 strict,除非你确定子应用没用 CSS-in-JS
    experimentalStyleIsolation: true // 这个够用,靠属性隔离
  }
});

为什么这么写?几个点:

  • props 透传全局状态:别用 qiankun 自带的 globalState,太弱。我直接在 window 上挂一个轻量 store(比如用 tiny-emitter 实现),主子应用都能读写,通信更灵活。
  • error 回调必须处理:子应用挂了不能让主应用也瘫痪。我见过太多团队上线后因为一个子应用 502 导致整个系统不可用。
  • 别开 strictStyleIsolation:这个模式会用 Shadow DOM,但很多 UI 库(比如 Element Plus 的弹窗)会挂到 body 下,导致样式丢失。experimentalStyleIsolation 用 data 属性隔离,兼容性好得多。

这几种错误写法,别再踩坑了

以下是我和同事踩过的经典反面案例,新手特别容易中招:

1. 子应用用绝对路径加载静态资源

比如子应用里写:<img src="/assets/logo.png">。在独立运行时没问题,但被主应用加载后,路径会变成主应用的域名,图片 404。正确做法是:

  • 开发时用相对路径:./assets/logo.png
  • 或者构建时注入 publicPath(webpack 配置):
    // webpack.config.js (子应用)
    output: {
      publicPath: process.env.NODE_ENV === 'production' 
        ? '//your-subapp-domain.com/' 
        : '/'
    }

2. 路由用 hash 模式硬切

有些团队为了省事,主应用用 history,子应用用 hash,然后靠 window.location.hash = '#/xxx' 跳转。结果用户刷新页面,子应用路由全丢。微前端里所有应用必须用同一种路由模式,而且最好都用 history。主应用负责拦截所有路由,子应用只响应自己的 activeRule 区间。

3. 全局事件监听不清理

子应用里写了 addEventListener('resize', handler),但没在 unmount 时移除。结果每次切换子应用,内存里多一个监听器,页面越用越卡。qiankun 的子应用必须导出 unload 方法:

// 子应用入口
export async function bootstrap() { /* ... */ }

export async function mount(props) {
  // 绑定事件
  window.addEventListener('resize', myHandler);
}

export async function unmount() {
  // 必须清理!
  window.removeEventListener('resize', myHandler);
}

如果你用 React,可以在 useEffect 里返回清理函数,但要确保这个 effect 在 unmount 时执行。Vue 的话,在 beforeUnmount 里处理。

实际项目中的坑

除了代码层面,部署和协作也有不少坑:

本地开发联调巨麻烦:主应用跑 3000,子应用跑 8081/8082,每次改子应用都要重启主应用?太折磨了。我的方案是:

  • 主应用加个 dev-proxy,把 /app1 代理到 localhost:8081
  • 子应用独立开发时,mock 主应用传来的 props(比如 globalStore)

这样主应用不用重启,子应用改完自动热更新。

公共依赖别重复打包:主应用和子应用都用了 lodash,结果 bundle 里有两份。解决方法是在 webpack 里 external 掉公共库,或者用 module federation 共享依赖。但要注意版本必须严格一致,否则运行时出错。

登录态怎么共享?别每个子应用单独存 token。我们统一用主应用管理 auth,子应用通过 props 拿 token,或者约定从 localStorage 的固定 key 读取。但要注意安全,别把敏感信息放 localStorage。

还有个头疼的问题:子应用的 API 地址。别在子应用里硬编码 https://jztheme.com/api,应该由主应用通过 props 传入,或者子应用读取环境变量。这样测试环境、生产环境切换才方便。

最后说两句

微前端不是银弹。如果你的团队只有三五个人,业务也不复杂,硬上微前端只会增加维护成本。它适合大型团队、多个独立产品线、技术栈异构的场景。上了微前端后,一定要有统一的规范:目录结构、构建配置、通信方式、错误监控……否则后期根本没法维护。

以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,比如你们怎么处理子应用之间的跳转?或者怎么优化首屏加载速度?这些我还在摸索中。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论