1. 微前端项目架构及选型介绍 

1.1. 微前端选型概述

微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。考虑到qiankun作为蚂蚁的微前端架构,具备以下特点:①简单。子应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。②能力完备。几乎包含所有构建微前端系统时所需要的基本能力,如样式隔离、js 沙箱、预加载等。③健壮性值得信赖。在蚂蚁内外经受过足够大量的线上系统的考验及打磨。所以,选择qiankun去构建集成应用项目的微前端架构。

1.2. 项目简要

本文配合实战demo讲解微前端项目和集成方案。

【技术选择】

主应用:umi(Ant Design Pro)

子应用:umi(Ant Design Pro)、vue(vue-element-admin)

路由模式:都用hash

【源码地址】
qiankun-demohttps://gitee.com/weihui007/qiankun-demo

2. 微前端基础配置

2.1. 主应用qiankun配置(umi项目)

(1)安装插件

yarn add @umijs/plugin-qiankun -D

 (2)注册子应用

插件构建期配置子应用

export default {
  qiankun: {
    master: {
      apps: [
        {
          name: 'reactApp', // 唯一 id
          entry: 'http://localhost:8091', // html entry
        },
        {
          name: 'vueApp',
          entry: 'http://localhost:8092',
        },
      ],
    },
  },
};

 (3)装载子应用

使用路由绑定的方式

export default {
  routes: [
    // 配置子应用reactApp关联的路由
    {
      name: 'react子应用',
      path: '/subreact',
      microApp: 'reactApp',
    },
    // 配置子应用vueApp关联的路由
    {
      name: 'vue子应用',
      path: '/subvue',
      microApp: 'vueApp',
    },
  ],
};

2.2. 子应用qiankun配置

2.2.1. vue项目

(1)入口文件main.js

子应用需要在自己的入口文件导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。

bootstrap:只会在子应用初始化的时候调用一次,下次子应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。

mount:应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法。

unmount:应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载子应用的应用实例

// 声明一个变量,可以用于卸载
let instance = null
// 挂载到自己的html中,基座会拿到这个挂载后的html插入进去
function render(props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 子组件的协议,必须暴露三个函数
export async function bootstrap(props) {
  console.log('bootstrap函数:', props)
}
export async function mount(props) {
  console.log('mount函数:', props)
  render(props)
}
export async function unmount(props) {
  console.log('unmount函数:', props)
  instance.$destroy()
  instance = null
}

(2)配置文件vue.config.js

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别子应用暴露出来的一些信息,子应用的打包工具需要增加如下配置

//修改打包配置
configureWebpack: {
  output: {
    //输出暴露的名称,假设名称为vueApp
    library: 'vueApp', //输出暴露的类型
    libraryTarget: 'umd'
  }
},
//允许跨域
devServer: {
  headers: {
    'Access-Control-Allow-Origin': '*'
  }
},

2.2.2. react项目(umi项目)

(1)安装 qiankun 插件 @umijs/plugin-qiankun

$ npm i @umijs/plugin-qiankun  
# 或者
$ yarn add @umijs/plugin-qiankun

(2)插件注册(config.ts)

export default {
  qiankun: {
    slave: {}
  }
}

(3)配置运行时生命周期钩子(可选)

插件会自动为你创建好 qiankun 子应用需要的生命周期钩子,但是如果你想在生命周期期间加一些自定义逻辑,可以在子应用的 src/app.ts 里导出 qiankun 对象,并实现每一个生命周期钩子,其中钩子函数的入参 props 由主应用自动注入。

export const qiankun = {
  // 应用加载之前
  async bootstrap(props) {
    console.log('bootstrap函数:', props)
  },
  // 应用 render 之前触发
  async mount(props) {
    console.log('mount函数:', props)
  },
  // 应用卸载之后触发
  async unmount(props) {
    console.log('unmount函数:', props)
  }
}

2.3. 主子应用实现通信配置

2.3.1. 主应用umi项目

(1)向umi子应用通信

 ① 新建src->app.ts文件

import { useState } from 'react';

export function useQiankunStateForSlave() {
  const [projectId, setProjectId] = useState<string>('');
  return { projectId, setProjectId };
}

② 在组件中使用qiankunStateForSlave

import { useModel } from 'umi';

export function ProComp() {
  const { setProjectId } = useModel('@@qiankunStateForSlave');
  const onChangePro = () => {
    setProjectId('123');
  };
  return <button onClick={onChangePro}>更改项目信息</button>;
}

(2)向vue子应用通信

 ① 新建src->action.ts文件

import { initGlobalState } from 'qiankun';

const initialState = {
  // 初始化数据
  projectId: ''
};

// 初始化 state
const actions = initGlobalState(initialState);

export default actions;

② 在组件中使用actions

import actions from '@/actions';

export function ProComp() {
  const onChangePro = () => {
    actions.setGlobalState({ projectId: '123' });
  };
  return <button onClick={onChangePro}>更改项目信息</button>;
}

2.3.2. 子应用vue项目

使用qiankun 官方提供的通信方式 - Actions 通信去实现

(1)新建src->actions.js文件

function emptyAction() {
  //设置一个actions实例
  // 提示当前使用的是空 Action
  console.warn('Current execute action is empty!')
}
class Actions {
  // 默认值为空 Action
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction
  }
  /**
   * 设置 actions
   */ 
  setActions(actions) {
    this.actions = actions
  }
  /**
   * 映射,注册观察者函数,响应 globalState 变化
   */ 
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args)
  }
  /**
   * 映射,设置 globalState
   */
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args)
  }
}
const actions = new Actions()
export default actions

(2)入口文件main.js

在mounted的生命周期里注入actions实例

import actions from './actions'
export async function mount(props) {
  actions.setActions(props) //注入actions实例
  render(props)
}

(3)使用范例

<template>  
  <div>  
    <div>这是子应用</div>  
    <p>接收到的消息: {{mes}}</p>  
    <button @click= "btnClick">点击向父应用发送消息</button>  
  </div>  
</template>  
<script>  
import actions from '../actions'//导入实例  
export default {  
  data() {  
    return {  
      mes: '',  
    }  
  },  
  mounted() {  
    actions.onGlobalStateChange((state) => { //监听全局状态  
      this.mes = state  
    }, true);  
  },  
  methods:{  
    btnClick(){  
      actions.setGlobalState({ info: '123'})//改变全局状态  
    }  
  }  
}  
</script>  

2.3.3. 子应用umi项目

使用useModel钩子函数 

import { useModel } from 'umi';

function MyPage() {
  const masterProps = useModel('@@qiankunStateFromMaster');
  return <div>{JSON.stringify(masterProps)}</div>;
}

3. 微前端项目集成方案

本文利用微前端架构主要实现如下功能:

把不同项目(子应用)集成到一个平台(主应用)。

【顶部导航栏】可包含主应用页面以及各个子应用,子应用可以是任何技术栈(demo中子应用为react和vue)。

【侧边菜单导航栏】可以是主应用页面的子菜单,也可以是子应用的菜单。

【主内容区域】即为页面主要展示区域。若当行了选择的是子应用,即为子应用展示区域。

具体实现效果如下图:

3.1. 主应用改造

主应用为antd pro项目。菜单模式设置为mix(混合菜单),记得切割菜单,即配置splitMenus为true。通过上述配置结合路由配置,即可实现上述平台布局(顶部全局导航+侧边菜单导航混合)。

路由数据结构(routes.ts)参考如下:

export default [
  {
    name: '欢迎', // 主应用页面1,在顶部全局导航展示
    path: '/welcome',
    icon: 'smile',
    component: './Welcome',
  },
  {
    name: '管理页', // 主应用页面2,在顶部全局导航展示
    path: '/admin',
    icon: 'crown',
    access: 'canAdmin',
    component: './Admin',
    routes: [ // 二级菜单,在侧边菜单导航展示
      {
        name: '二级管理页',
        path: '/admin/sub-page',
        icon: 'smile',
        component: './Welcome',
      },
    ],
  },
  {
    name: 'react子应用', // 子应用1,在顶部全局导航展示
    path: '/subreact',
    microApp: 'reactApp',
    routes: [ // 子应用1的菜单,在侧边菜单导航展示
      {
        name: 'Welcome',
        icon: 'smile',
        path: '/subreact/Welcome',
      },
    ],
  },
  {
    name: 'vue子应用', // 子应用2,在顶部全局导航展示
    path: '/subvue',
    microApp: 'vueApp',
    routes: [ // 子应用2的菜单,在侧边菜单导航展示
      {
        name: 'Example',
        icon: 'table',
        path: '/subvue/example',
        routes: [
          {
            name: 'Table',
            icon: 'table',
            path: '/subvue/example/table',
          },
        ],
      },
    ],
  },
];

3.2. 子应用改造

根据window.__POWERED_BY_QIANKUN__判断子应用是不是从qiankun运行环境进来的,进行两套环境改造。如果独立运行,则走原来的逻辑,否则需要走改造后的逻辑。 

3.2.1. 布局改造

如果子应用是从qiankun运行环境进入,即window.__POWERED_BY_QIANKUN__为true,则去除子应用头部和导航菜单布局。

umi子应用:删除掉所有的自带界面,将ProLayout布局组件设置pure为true。 

vue子应用(vue-element-admin): 用条件语句v-if、v-else判断,如果是qiankun环境,则只保留主内容区域。

3.2.2. 登录逻辑改造

登录权限打通,主应用登录后,切换到子应用不需要再进行登录校验,实现免登录功能。

比如主应用是通过cookie验证用户登录的,当子应用是从qiankun环境进入,则通过对应的cookie判断是否有登录,若已登录则跳过登录直接进入对应页面,若没登录或者登录失效则传值给父应用告诉父应用需要重新登录,然后父应用跳转登录页(可在响应拦截中进行父子应用通讯,实现接口回传token失效等原因告诉父应用需要跳转登录页)。

3.2.3. 其它改造

(1)子应用为vue项目需要判断qiankun环境把路由加上前缀

一般需要修改2个地方:①路由表加上前缀;②路由守卫判断如果没有前缀需加上前缀(用于子应用内部需要做路由跳转) 

TIPS:前缀指在主应用中跳转子应用设定的path值。

①参考如下:

如果是qiankun环境,则给路由数据的path(路径)和redirect(重定向)值加上前缀。这样就可以和主应用项目对应的菜单导航栏的跳转路径保持一致。

if (window.__POWERED_BY_QIANKUN__) {
  routesData.map(item => {
    if (item.path.includes('/')) {
      item.path = '/subvue' + item.path
    }
    if (item.redirect) {
      item.redirect = '/subvue' + item.redirect
    }
    return item
  })
}

②参考如下:

如果是qiankun环境,则通过路由守卫 beforeEach对路由跳转前进行判断拦截,如果,跳转的地址没有加有前缀,则加上。否则,会出现404找不到页面。

if (window.__POWERED_BY_QIANKUN__) {
  router.beforeEach((to, from, next) => {
    if (!to.path.includes('subvue')) {
      next({
        path: '/subvue' + to.path,
        query: to.query
      })
    } else {
      next()
    }
  })
}

(2)其它

除上述提到的,可以根据自身业务需求进行一些改造,常见的有网络请求封装改造、菜单按钮展示改造以及资源加载路径改造等等。 

4. 应用集成逻辑图

仅以umi版本为例,展示如下

【推荐文章】

antd pro项目使用qiankun,loading加载页不消失https://blog.csdn.net/w544924116/article/details/120164997antd pro(ProLayout) mix混合菜单不生效https://blog.csdn.net/w544924116/article/details/120211891Error: Module “xxx“ does not exist in container. / antd pro v5启用qiankun报错 / 同时使用mfsu和qiankun报错https://blog.csdn.net/w544924116/article/details/120123331引用window自定义变量以及ts在window上自定义变量数据类型报错的解决方案https://blog.csdn.net/w544924116/article/details/120251686

感谢您读完本文!如果本文对您有帮助,请点个赞呗,您的点赞是对我最大的支持和认可!

我的公众号:大前端教程,欢迎关注,会定期更新前端知识,希望能帮到您。

Logo

前往低代码交流专区

更多推荐