微前端(qiankun)Demo经验总结

599 阅读1分钟

1.为什么要有微前端?

随着前端技术的逐步发展,前端业务涉及越来越广,前端应用越来越大,所有功能集合在一起。微前端就是分了拆分一个个业务模块的代码作为独立的微应用。

2.微前端的好处

1.与技术栈无关,可以接入任何技术栈的子应用
2.基于 single-spa 封装,提供了更加开箱即用的 API。
3.微应用之间可以独立开发,独立部署。

3.Demo实践

1.主应用开发:

1.安装qiankun   yarn add qiankun # 或者 npm i qiankun -S
2.在主应用中注册子应用
import { registerMicroApps, start } from 'qiankun';
//注册子应用(main.js)
registerMicroApps([
  {
    name: 'vue app', // app name registered
    entry: '//localhost:8082',
    container: '#vueApp',
    activeRule: '/vue',
  },
  {
    name: 'vue2 app', // app name registered
    entry: '//localhost:8083',
    container: '#child',
    activeRule: '/vue2',
  },
]);
start();
3.写入一个用于挂载微应用的容器(App.vue)
 <el-menu :router="true" mode="horizontal" :default-active="activeIndex">
  <!--基座中可以放自己的路由-->
  <el-menu-item index="/">Home</el-menu-item>
  <!--引用其他子应用-->
  <el-menu-item index="/vue2">子应用2</el-menu-item>
  <el-menu-item index="/vue">vue应用</el-menu-item>
</el-menu>
<div id="vueApp"></div> <!--注意和container一致-->
<div id="child"></div>

2.子应用开发

1.main.js
let router = null;
let instance = null;
function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/vue2' : '/', // path 需要和基座(base)应用保持一致
    routes: routes,
    store: store,
    mode: 'history'
  });
  instance = new Vue({
    router,
    render: h => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}
// 解决基础路径不正确的问题
if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//导出生命周期
/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('vue2 app 启动');
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  render(props);
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log('卸载')
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  console.log('update props', props);
}
2.注意在本地测试时要让微应用允许跨域访问(vue.config.js)
  devServer: {
    hot: true,
    disableHostCheck: true,
    port,
    overlay: {
      warnings: false,
      errors: true,
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },

Demo地址: github.com/s980711272/…

4.缓存问题解决

function render(){
  // 这里必须要new一个新的路由实例,否则无法响应URL的变化。
  router = new Router({
    mode: 'history',
    base: !window.__POWERED_BY_QIANKUN__ ? '' : 'bms',
    routes: constantRoutes,
  });
  router.addRoutes(asyncRoutes);
  //缓存实例化
  if (window.__POWERED_BY_QIANKUN__ && window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__) {
    const cachedInstance = window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__;

    // 从最初的Vue实例上获得_vnode
    const cachedNode =
      (cachedInstance.cachedInstance && cachedInstance.cachedInstance._vnode) ||
      cachedInstance._vnode;

    // 让当前路由在最初的Vue实例上可用
    router.apps.push(...cachedInstance.catchRoute.apps);

    instance = new Vue({
      router,
      store,
      render: () => cachedNode
    });

    // 缓存最初的Vue实例
    instance.cachedInstance = cachedInstance;

    router.onReady(() => {
      const { path } = router.currentRoute;
      const { path: oldPath } = cachedInstance.$router.currentRoute;
      // 当前路由和上一次卸载时不一致,则切换至新路由
      if (path !== oldPath) {
        cachedInstance.$router.push(path);
      }
    });
    instance.$mount('#micro-bms');
  } else {
    //console.log('正常实例化');
    // 正常实例化
    instance = new Vue({
      el: '#micro-bms',
      router,
      store,
      render: h => h(App)
    });
  }
}
export async function mount(props) {
  Vue.prototype.$poweredByQiankun = true;
  render()
}
export async function unmount() {
  //console.log('micro-bms 卸载');
  //缓存
  const cachedInstance = instance.cachedInstance || instance;
  window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__ = cachedInstance;
  const cachedNode = cachedInstance._vnode;
  if (!cachedNode.data.keepAlive) cachedNode.data.keepAlive = true;
  cachedInstance.catchRoute = {
    apps: [...instance.$router.apps]
  }
  instance.$destroy();
  router = null;
  instance.$router.apps = [];
}