【微前端】Qiankun 微应用之间如何跳转

11,378 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

导读

填坑文,本文主要是填《【微前端】Qiankun 其实是一个项目重构的利器》里留的坑。因为 Qiankun 要求微应用修改自己 routerbase,来达到 url 隔离的目的。这就造成使用微应用 router 的方法无法跳回主应用,也就无法直接跳到其他微应用。那么如何解决呢?

正文

解决跳转问题有两种方案,第一种比较简单,直接用 location.href 等方法进行 url 跳转即可。但是这个方案的缺点非常明显,那就是在跳转的时候会“白屏”,这就意味着废了大力气引入了微前端,得到的效果跟用 url 隔离一样(详见《【微前端】Qiankun 其实是一个项目重构的利器》一文背景部分的方案)。所以明显是不可取的。

(2021.11.04 补充,发现了一个更轻量的解决方法,直接调用 window.history.pushState 即可,当然,前提是路由系统底层用的是 history 模式)

第二种方案,也就是正式方案说穿了也很简单。在《重构的利器》一文中已经埋下了伏笔。截取一段多实例模式下的代码大家就了然了:

// 主应用
const microAppContainer = useRef(null); 
const history = useHistory();

useEffect(() => {
  let mainApp;
  if (microAppContainer.current) {
    // 手动加载微应用
    mainApp = loadMicroApp({
      name: "vue3 ts app",
      entry: "//localhost:8081",
      container: microAppContainer.current,
      /* 将路由注入子应用 */
      props: { mainAppRouter: history },
    });
  }
  return () => {
    // 别忘了卸载
    mainApp.unmount();
  };
}, []);

没错,把主应用的 historyprops 的形式传给子应用即可,接下来子应用需要做的就是把它提供给全局使用。此处采用 Vue3 的语法来实现,如下:

// 入口改造
function render(props) {
  const { container, maiAppRouter } = props;
  app = createApp(App);
  if (maiAppRouter) {
    /* 用 provide 挂到全局 */
    app.provide("maiAppRouter", maiAppRouter);
  }
  instance = app
    .use(store)
    .use(router)
    .mount(container ? container.querySelector("#app") : "#app");
}

// 组件使用
import { inject } from "vue";

const mainAppRouter = inject("maiAppRouter");
mainAppRouter.push('/'); // 等于 history.push('/')

单实例模式的改造就显而易见了。在子应用中没有任何差别,只是在主应用注册时传入 props 即可,不过因为 useHistory 必须在 React 组件中调用,所以需要换一下,如下:

/* 不能有 hooks,用 createBrowserHistory 替换 */
import { createBrowserHistory } from "history";

const history = createBrowserHistory();
// 注册微任务
registerMicroApps([
  {
    name: "vue2 app", // app name registered
    entry: "//localhost:8080",
    container: "#vue2Container",
    activeRule: "/app-vue",
    props: { mainAppRouter: history },
  },
]);

最后再附上 Vue2 版本的初始化 provide 代码:

  instance = new Vue({
    router,
    store,
    provide: { mainAppRouter }, // 初始化时直接挂载到实例
    render: (h) => h(App),
  }).$mount(container ? container.querySelector("#app") : "#app");

结语

微前端的坑终于填完了。目前还没有在项目中使用,但是相信很快就会有实践机会了,因为有个老系统需要重构,到时候应该还会遇到一些坑。到时候再补充吧。

当你感觉需要撰写注释,请先尝试重构,试着让所有注释都变得多余。——Martin Fowler