微前端(qiankun)-主应用非umi子应用为umi

背景

由于umi提供了自己的微前端插件plugin-qiankun,而主应用并不是使用umi搭建的,就会涉及主应用直接使用qiankun,而子应用由于入口被umi封装,无法在入口文件导出微前端需要的生命周期,所以只能直接使用plugin-qiankun。

实现方式

  • 主应用
componentDidMount() {
  this.microApp = loadMicroApp({ 
    name: 'app', 
    entry: '//localhost:xxxx',
    container: this.containerRef.current
  });
}
render() {
  return (
) }
  • 微应用
    config配置,umi一键开启qiankun
qiankun: {
    slave: {},
  }

src/app.ts

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);
  },
};

遇到的问题

1、本地开发环境下,可以加载子应用,但是会有一堆dev-server资源加载404(我猜是dev-server需要某个配置,比如publicPath,但是没有找到配置的地方),转换思路,部署以后访问,跳过dev-server。
2、部署完成后,又发现以下问题:

  • 访问子应用遇到跨域问题(ngingx配置解决跨域)
server {
        listen       8008;
    #  listen       somename:8080;
        server_name  localhost;

        location / {
           add_header 'Access-Control-Allow-Origin' '*';
           add_header 'Access-Control-Allow-Credentials' 'true';
           add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
           add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
           root D:/xxx/xxx;
           index index.html index.htm;
        }
    }

  • 子应用打包后的字体文件资源路径错误
    解决方案:配置字体文件的publicPath
    在config中配置chainWebpack
chainWebpack(config) {
    config.module.rule('fonts').use('file-loader').options({
      publicPath: process.env.NODE_ENV === 'production' ? 'http://localhost:xxxx/' : '/', // 给字体文件指定文件加载路径
      name: 'static/[name].[hash:8].[ext]',
      esModule: false
    });
    // console.log(config.toString())
  },

这里有个技巧,如果不熟悉chainWebpack的写法,可以用console.log(config.toString())在终端打印出webpack一样的配置作为配置参考


  • 父子应用的样式冲突
    贴一张qiankun关于样式冲突解决方案的图,仿照着做就好了。


    image.png

    umi的config中有这样的一个配置theme,上面提到的@ant-prefix设置可以放在此处,如果你不放心,依然可以使用console.log(config.toString())验证。

theme: {
    // ...darkTheme,
    'primary-color': defaultSettings.primaryColor,
    '@dt-prefix': myPackage.name,
  },

然后正确设置ConfigProvider

const wrapApp = (props) => {
  return (
      
        {props?.children}
      
  );
} 

所有的页面都通过wrapApp包裹,路由如下配置:

{
    path: '/',
    component: '../layouts/wrap',
    routes: [
      { path: '/', redirect: '/layouts' },
      {
        path: '/layouts',
        component: './xxx',
      }
    ]
}

自此,所有的问题都得以解决。

主应用与微应用的通信问题

官方示例

主应用:

import { initGlobalState, MicroAppStateActions } from 'qiankun';

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

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

微应用:

// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });

  props.setGlobalState(state);
} 
实践

实际使用中会涉及到几个问题(问题都在子应用中):

1、props.setGlobalState方法该存放在哪?
首先我们要与主应用通信,需要调用这个方法,一开始我想的都是放到状态管理dva的model中,但是现实很残酷,在mount生命周期执行的时候根本还拿不到dva的实例,所以存到dva是不现实的,所以最后我是放到一个变量中,分别定义了该变量的get和set方法。

2、主应用传过来的state存放在哪?
因为我们要根据state的改变来刷新页面,所以这个state必须放到dva中,好在props.onGlobalStateChange监听的回调中是能拿到dva实例的,所以我们找到dispatch方法就可以更新state

放代码
app.ts

import { setPageStatus, setSetGlobalState, resetDispatch } from '@/utils/signalCommunication'

const myPackage = require('../package.json')

export const qiankun = {
  // 应用加载之前
  async bootstrap() {
    // eslint-disable-next-line no-console
    console.log(`${myPackage.name} bootstrap`)
  },
  // 应用 render 之前触发
  async mount(props) {
    // eslint-disable-next-line no-console
    console.log(`${myPackage.name} mount`)
    props.onGlobalStateChange((state) => {
      // state: 变更后的状态; prev 变更前的状态
      if (state.type !== 'default') { // default专门给主应用使用
        setPageStatus(state.type)
      }
    });
    setSetGlobalState(props.setGlobalState)
  },
  // 应用卸载之后触发
  async unmount() {
    // eslint-disable-next-line no-console
    console.log(`${myPackage.name} unmount`)
    resetDispatch()
  },
}

utils/signalCommunication.ts

import { getDvaApp } from 'umi';

let dispatch
let setGlobalState // 存放setGlobalState方法

const getDispatch = () => {
  if (dispatch) {
    return dispatch
  }
  const app: any = getDvaApp()
  // eslint-disable-next-line no-underscore-dangle
  dispatch = app ? app._store?.dispatch : null
  return dispatch
}
// 微应用卸载的时候需要去重置dispatch,不然再次挂载拿不到真实的dva实例
export const resetDispatch = () => {
  dispatch = null
}

export const setPageStatus = (payload) => {
  const dispatch = getDispatch()
  if (dispatch) {
    dispatch({ type: 'global/setPageStatus', payload })
  }
}

export const getSetGlobalState = () => {
  return setGlobalState
}
export const setSetGlobalState = (func) => {
  setGlobalState = func
}

你可能感兴趣的:(微前端(qiankun)-主应用非umi子应用为umi)