umi3 + qiankun 实现一个简单的微前端demo | 🏆 技术专题第四期征文

5,815 阅读5分钟

微前端最近是个很火的概念,社区其实也有不少成熟的方案去实现。作为一个umi的重度使用者,就必须来体验一下阿里基于single-spa开发的qiankun了,而umi跟qiankun的最佳结合方案,无疑是qiankun为umi量身订造的插件@umijg/plugin-qiankun。那我们就跟着官网的文档开造吧。

Demo实现

创建项目

  1. npx @umijs/create-umi-app
  2. npm install
  3. npm install @umijs/plugin-qiankun --save
  4. npm start

共创建3个项目,一个作为主应用,2个作为子应用

配置主应用

  1. 注册子应用。在主应用的.umirc.ts中,开启qiankun。这是umi3插件的一种开启方式,只要插件的key(这里的plugin-qiankun的key就是qiankun)有对应的配置就会开启qiankun。
export default {
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'app1', // 唯一 id,需要与微应用中的package.json中的name一致
          entry: '//localhost:8001', // html entry
        },
        {
          name: 'app2', // 唯一 id,需要与微应用中的package.json中的name一致
          entry: '//localhost:8002', // html entry
        }
      ],
    },
  },
};
  1. 装载子应用。在需要加载子应用的路由上,配置microApp属性为子应用的name,即可与路由绑定
export default {
    routes: [
    {
      path: '/',
      component: '../layouts/index.js',
      routes: [
   		{
           path: '/app1',
           microApp: 'app1',
           // 配置Loading属性
           microAppProps: {
            autoSetLoading: true,
            className: 'myContainer',
            wrapperClassName: 'myWrapper',
          }
        },
        {
          // app2
          // ...
        }
      ],
    },
  ],
}

配置子应用

  1. 插件注册。在package.json中配置"name": "app1"。同时也需要配置.umirc.ts文件开启qiankun。
export default {
  qiankun: {
    slave: {}
  }
}
  1. 环境变量配置。在.env中:
PORT=8001
HMR=none

如果遇到了因为dev scripts端口号等问题产生冲突以致于报错,可以先关闭HMR试试

  1. 如果子应用需要在生命周期中加一些自定义逻辑,则需要在src/app.ts中导出一个对象qiankun
export const qiankun = {
  // 应用加载之前
  async bootstrap(props) {
    console.log('app1 bootstrap', props);
  },
  // 应用 render 之前触发
  async mount(props) {
    console.log('app1 mount', props);
  },
  // 应用卸载之后触发
  async unmount(props) {
    console.log('app1 unmount', props);
  },
};

现在运行3个项目,就能通过主应用来访问子应用了,例如访问http://localhost:8000/app1显示的是子应用的页面。这里需要注意的是,子应用的端口号是_8001_,那么原来的子应用发出的处于同一域名下的后端请求也是在_8001_端口上,但通过主应用来访问,后端请求就会变为_8000_端口,需要做一个代理转发,可以在.umirc.ts中配置proxy

这时候应用之间是基本独立的,我们还需要加入供应用间通讯的全局状态

应用间通讯

这里主要使用@umijs/plugin-modeluseModel来将数据全局化。umi应该已经内置了@umijs/preset-react插件集,其中就包括了@umijs/plugin-model

主应用生产数据

src/app.ts中导出一个函数useQiankunStateForSlave,这个函数返回的内容会注入到props中传递给子应用。这里有个坑,useState返回的setXXX方法不能命名为setGlobalState,不然会在mount阶段被qiankun的另一个同名函数覆盖。

// src/app.ts
export function useQiankunStateForSlave() {
const [globalState, setQiankunGlobalState] = useState({ str: 'aaa'})

  return {
    globalState,
    setQiankunGlobalState
  };
}

如果主应用也需要用到这个globalState的话,也可以在组件中借助useModel访问@@qiankunStateForSlave:

const { globalState } = useModel('@@qiankunStateForSlave');

子应用消费数据

子应用通过useModel访问@@qiankunStateFromMaster这个model以获取主应用传过来的props

export default () => {
  const masterProps = useModel('@@qiankunStateFromMaster');

  useEffect(() => {
    masterProps.setQiankunGlobalState({ str: 'bbb' })
  }, [])

  return (
    <p>{JSON.stringify(masterProps.globalState)}</p>
  );
}

这样,主应用负责管理globalState,子应用也能取到globalState并调用masterProps上的方法来通知主应用修改globalState

Demo 演示

子应用app2将主应用的globalState从aaa改成bbb 微前端Demo

一些思考

如何管理应用权限

每个中后台系统,基本都是需要获取用户权限来限制某些页面的访问权。那么在微前端这种使用场景,最好是由主应用来统一管理每个子应用的用户权限等信息。最好还能不改变原来单独访问子应用时的逻辑。

假设token放在localStorage中,这里想到了2个方案:

  1. 【统一处理】一旦进入任一子应用,主应用取出token发出一个checkLogin请求,后端检查2个子应用的登录态,只要任一没登录就触发login逻辑,如果都登录了就返回2个子应用的userInfo,主应用再分别传递userInfo给子应用。(需要后端加接口支持)
  2. 【分开处理】一旦进入任一子应用,主应用取出2个token发出2个checkLogin请求,经转发至子应用的域名后,子应用的2个后端各自检查登录态,有则返回userInfo。主应用只要没有拿到2份userInfo,就触发login逻辑,再分别传递userInfo给子应用。

子应用通过当前域名识别出是否位于主应用,是则不走自己的登录校验逻辑。

能支持Umi2吗?

本人也尝试接入umi2版本的项目,即按照对应版本的qiankun插件

npm install @umijs/plugin-qiankun@umi2 --save

也能运行起来

但是这个版本的qiankun插件,子应用并不能使用@@qiankunStateForSlave来获取masterProps,这个版本是使用const masterProps = useRootExports()来获取masterProps。显然这样的话主应用还得额外处理供umi2子应用使用的全局state,感觉不是很方便。

参考资料

umi官网 @umijs/plugin-qiankun

🏆 技术专题第四期 | 聊聊微前端的那些事......