【实践】使用 UMI + Qiankun 搭建主基座应用和微应用

3,168 阅读4分钟

使用 UMI + Qiankun 搭建主基座应用和微应用

框架地址

React 框架:UMI 文档地址

Qiankun 框架: Qiankun地址

废话不多说了,直接看代码

创建主基座应用

Step1: 使用 umi 的脚手架创建项目

# 创建主项目
yarn create @umijs/umi-app

# 添加 `qiankun-plugin`
yarn add @umijs/plugin-qiankun -D

下面是一份比较完整的 package.json

{
  "private": true,
  "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "postinstall": "umi generate tmp",
    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
    "test": "umi-test",
    "test:coverage": "umi-test --coverage"
  },
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,less,md,json}": [
      "prettier --write"
    ],
    "*.ts?(x)": [
      "prettier --parser=typescript --write"
    ]
  },
  "dependencies": {
    "react": "17.x",
    "react-dom": "17.x",
    "umi": "3.5.19",
    "@umijs/plugin-qiankun": "2.33.1"
  },
  "devDependencies": {
    "@types/react": "17.0.0",
    "@types/react-dom": "17.0.0",
    "@umijs/preset-react": "1.x",
    "@umijs/test": "3.5.19",
    "lint-staged": "10.0.7",
    "prettier": "2.2.0",
    "typescript": "4.1.2",
    "yorkie": "2.0.0"
  }
}

Step2: 配置.umirc.ts 文件

# .umirc.ts
# 在开发模式下,主应用加载微应用的图片等静态资源的代理
proxy: {
  '/xx': {
    target: 'http://localhost:xxxx',
    changeOrigin: true,
  },
},
# 开启`qiankun`主应用
qiankun: {
  master: {},
},

Step3: 创建 app.ts,定义qiankun相关微应用和路由,以及全局变量等数据

此处只定义了 lang 和 userInfo 两个变量,lang 代表语言,userInfo 代表用户信息

# src/app.ts
import { useState } from 'react';
import actions from '@/qiankun/action';

export function useQiankunStateForSlave() {
  const [qiankunMainState, setQiankunMainState] = useState<QiankunStateProps>({
    lang: 'zh-CN',
    userInfo: null,
  });

  // 实际给子应用调用修改state的方法
  // 传参和实现可以自定义,子应用直接调用setGlobalState是不生效的,所以才需要这个额外的方法,这是一个坑
  const microSetQiankunState = (state: QiankunStateProps) => {
    const allStates = Object.assign(qiankunMainState, state);
    setQiankunMainState(allStates);
    actions.setGlobalState(allStates);
  };

  return {
    qiankunMainState,
    microSetQiankunState,
  };
}

export const qiankun = () => {
  return {
    apps: [
      {
        name: 'user',
        entry: '//localhost:xxxx',
      }
    ],
    routes: [
      {
        path: '/user',
        microApp: 'user',
        microAppProps: {
          autoSetLoading: true,
          className: 'userContainer',
          wrapperClassName: 'userWrapper',
        },
      }
    ],
    lifeCycles: {
      beforeLoad: (app: any) => {
        console.log('app before load', app);
      },
      afterMount: (app: any) => {
        console.log('app after mount', app);
      },
    },
    sandbox: {
      experimentalStyleIsolation: true,
    },
    prefetch: true,
  };
};

Step4: 在主应用中修改全局变量的值。

在上面的app.ts中,我们定义了microSetQiankunState,可以使用这个方法改变数据值

# src/models/useAuthModel.ts
...
const currentUser = { realname: 'xxx', username: 'xxx', sex: 'xxx', ... }
microSetQiankunState({ userInfo: currentUser })
...

Step5: 创建action, 监听全局变量的变化,比如主应用数据更改或者从微应用传递上来的数据

# src/qiankun/action.tsx
import { initGlobalState, MicroAppStateActions } from 'qiankun';

export const initialState: QiankunStateProps = {
  lang: "zh-CN",
  userInfo: null
};

const actions: MicroAppStateActions = initGlobalState(initialState);

// 使用下面的方法,可以在主应用的任意界面,监听数据的状态变化
// actions.onGlobalStateChange((state: QiankunStateProps, prev: QiankunStateProps) => {
//   console.log('主应用状态变更 :: ', state, prev);
// }, true);

export default actions;

Step6: 监听从微应用传递过来的lang,从而改变主基座应用和微应用的语言

# src/layout/index.tsx
import { useEffect } from 'react'
import actions from '@/qiankun/action'
const BasicLayout = ({ children }: any) => {

  useEffect(() => {
    actions.onGlobalStateChange((state: QiankunStateProps, prev: QiankunStateProps) => {
      console.log('主应用状态变更 :: ', state, prev);
      // 修改语言
      // changeLang(state.lang || 'zh-CN', false)
    }, true);
  }, [])
  
  return <><div>Baisc Layout</div><div>{children}</div></>
}

export default BasicLayout

创建微应用

Step1: 使用umi脚手架创建应用

# 创建主项目
yarn create @umijs/umi-app
 
# 添加 `qiankun-plugin`
yarn add @umijs/plugin-qiankun -D

下面是一份比较完整的 package.json,仅供参考

{
  "name": "yuestone-wms-app",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "start": "umi dev",
    "build": "umi build",
    "postinstall": "umi generate tmp",
    "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
    "test": "umi-test",
    "test:coverage": "umi-test --coverage"
  },
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,less,md,json}": [
      "prettier --write"
    ],
    "*.ts?(x)": [
      "prettier --parser=typescript --write"
    ]
  },
  "dependencies": {
    "@umijs/plugin-qiankun": "2.33.1",
    "react": "17.x",
    "react-dom": "17.x",
    "umi": "3.5.19"
  },
  "devDependencies": {
    "@types/react": "17.0.0",
    "@types/react-dom": "17.0.0",
    "@umijs/preset-react": "1.x",
    "@umijs/test": "3.5.19",
    "lint-staged": "10.0.7",
    "prettier": "2.2.0",
    "typescript": "4.1.2",
    "yorkie": "2.0.0"
  }
}

Step2: 配置.umirc.ts文件

# 配置`qiankun`的`slave`
qiankun: {
  slave: {}
}

Step3: 创建public-path.js

/* eslint-disable no-undef */
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

Step4: 创建global.d.ts文件

interface Window {
  __POWERED_BY_QIANKUN__: string;
}

Step5: 创建 action.tsx

# src/qiankun/action.tsx
import { initGlobalState, OnGlobalStateChangeCallback, MicroAppStateActions } from 'qiankun';

class Actions {
  // 默认值为空 Action
  actions: MicroAppStateActions = initGlobalState({});

  /**
   * 设置 actions
   */
  setActions(actions: any) {
    this.actions = actions;
  }

  /**
   * 映射
   */
  onGlobalStateChange(cb: OnGlobalStateChangeCallback, fireImmediately?: boolean) {
    return this.actions.onGlobalStateChange(cb, fireImmediately);
  }

  /**
   * 映射
   */
  setGlobalState(state: Record<string, any>) {
    return this.actions.setGlobalState(state);
  }
}

const actions = new Actions();
export default actions;

Step6: 创建app.tsx,定义qiankun相关界面内容以及相关生命周期

import ReactDOM from "react-dom";
import { BrowserRouter } from 'react-router-dom';
import App from './pages/index'
import './public-path';
import actions from "@/qiankun/action";

function render(props: any) {
  const { base, container } = props;

  ReactDOM.render(
    <BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? base : '/'}>
      <App />
    </BrowserRouter>,
    container ? container.querySelector('#root') : document.querySelector('#root'),
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export const qiankun = {
  // 应用加载之前
  async bootstrap(props: any) {
    console.log('子应用[WMS] bootstrap', props)
  },

  // 应用 render 之前触发
  async mount(props: any) {
    console.log('子应用[WMS] mount', props)
    actions.setActions(props)
    render(props);
  },

  // 应用卸载之后触发
  async unmount(props: any) {
    console.log('子应用[WMS] unmount', props)
    const { container } = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
  },
}

Step7: 使用从微应用传递过来的值

比如可以在layout界面设置微应用的语言

# src/layout/index.tsx
import { useEffect } from 'react'
import actions from '@/qiankun/action'
const BasicLayout = ({ children }: any) => {
  useEffect(() => {
    actions.onGlobalStateChange((state: QiankunStateProps, prev: QiankunStateProps) => {
      if (prev.lang !== state.lang) {
        // 更新语言
        changeLang(state.lang || 'zh-CN');
      }
    }, true);
  })
  
  return <><div>Basic Layout</div><div>{children}</div><>
}

export default BasicLayout

感谢

到此,本篇文章先告一段落,如果有什么错误或不足,欢迎评论区指正!感谢您的阅读~~

欢迎使用微信搜一搜:轻前端