环境
qiankun:2.10.10
主应用:angular 7
子应用:umi 3
主应用配置
子应用有两种加载方式,一种是根据页面路由自动加载,一种则为手动加载,自动加载的方式,在切换页面(或后台系统切换 tab )路由变化时会自动卸载掉子应用,不方便后续的状态缓存,所以选择手动加载子应用。
// 在入口文件
this.loadApp = loadMicroApp({
name: "micro", // 子应用名字
entry: "//localhost:8000", // 子应用的入口,若是部署上线子应用访问有前缀,入口配置也要加上对应前缀
container: "#subapp-viewport", // 挂载子应用时的id选择器出口
props: { brand: "qiankun", actions: {} },
});
start();
// html 容器
<div id="subapp-viewport" ></div>
在主应用 angular 新建一个空白组件,把打开到子应用的所有路由配置该组件
// /portal/list/xx 按照这样的格式命令路由
const routes: Routes = [
{
path: "portal",
data: { pageTitle: "微应用入口", keep: true },
children: [
{
path: 'list/:id',
component: PortalComponent,
},
{
path: 'detail/:id',
component: PortalComponent
},
]
},
];
子应用配置
在配置文件 umi.js 开启子应用配置
export default defineConfig({
qiankun: {
slave: {},
},
});
运行配置文件 app.js 写入生命周期方法
export const qiankun = {
// 应用加载之前
async bootstrap(props) {
console.log('app bootstrap', props);
},
// 应用 render 之前触发
async mount(props) {
console.log('app mount', props);
await storeChange(props);
},
// 应用卸载之后触发
async unmount(props) {
console.log('app unmount', props);
props.offGlobalStateChange && props.offGlobalStateChange();
},
};
至此,在打开主应用时,子应用就会加载上并成功执行相应的生命周期方法,因为这里的加载并没有根据路由来,可以根据路由判断手动加载的时机,并在关闭子应用的时候卸载掉,但是这样会有点卡顿。若是只有一个子应用的话,内存占用不是很大,可以按照上面的方法加载,在主应用的销毁方法里进行卸载。
子应用路由配置
若是进入到主应用对应的页面,子应用未加载出来,页面空白且没有其他报错,有可能是子应用没有加载到对应的页面,所以子应用的路由要配置的和主应用的一致。
// 如
主应用打开路由:/main/portal/list/page1
子应用要有相应配置路由:{ path: 'main/portal/list/page1', component: '@/pages/Table' }
路由要一致,子应用页面才会加载出来,动态包含其他变量的路由子路由要加上对应配置,这样才能匹配上。
组件通信
主应用和子应用通信 通过 qiankun 内置方法 initGlobalState 来实现,通过调用 initGlobalState 初始化一个对象,调用该对象的方法 setGlobalState 来进行传值,onGlobalStateChange 方法则进行监听。
import { initGlobalState } from 'qiankun';
// 这里可以写初始化数据
const initialState = {}
// 初始化需要传递的对象
export const actions = (props) => initGlobalState(Object.assign(initialState, props));
// state: 变更后的状态; prev 变更前的状态
actions().onGlobalStateChange((state, prev) => {
console.log('state', state);
console.log('prev', prev);
});
在注册子应用的时候把这个对象传到子应用
this.parentState = actions({ _ngTabPageService: this.ngTabPageService });
this.loadApp = loadMicroApp({
name: "micro", // 子应用名字
entry: "//localhost:8000", // 子应用的入口
container: "#subapp-viewport", // 挂载子应用时的id选择器出口
props: { brand: "qiankun", actions: this.parentState },
});
子应用在注册时就可以获取到该对象,也能进行调用 setGlobalState 和主应用通信(主应用要写相应的监听方法)
状态缓存
状态缓存多用于后台管理的多 tab 场景,在切换到其他 tab 页面,再切换回来时,子应用操作未完成的页面继续保持。状态缓存有两种方案,一是 umi-plugin-keep-alive,以及 @alitajs/keep-alive,两者的核心内容都是基于 react-activation,只是使用的方式不一样。
umi-plugin-keep-alive 的使用需要在组件页面通过 umi 引用 KeepAlive 组件,并用它来包裹住需要缓存的组件,再添加组件属性 name 来做唯一标识,在关闭的时候通过调用释放缓存方法 dropScope 或者其他方法来释放缓存,具体使用方式可以查看文档。@alitajs/keep-alive 的使用方式个人看来则比较简单,只需要安装好相关依赖,在 umi.js 配置 文件的 plugins 添加上,再开启 keepalive,之后在组件入口,比如 layout 模块进行配置就可以,具体使用查看文档,释放缓存则和 umi-plugin-keep-alive 有所不同,也是通过 umi.js 暴露出的方法 dropByCacheKey 来释放缓存,但是它是通过路由来做唯一标识的,所以 dropByCacheKey 的参数应当是要关闭的页面路由,如果发现关掉页面再次打开缓存还在,可以断点调试看看路由是否一致。
@alitajs/keep-alive 配置方法
// umi.js
import microRoutes from './src/router';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
publicPath: '/micro/',
base: '/micro/',
plugins: [
'@alitajs/keep-alive'
],
qiankun: {
slave: {},
},
routes: [
{
path: 'main',
component: '@/layouts/index',
routes: [
...microRoutes
],
},
],
keepalive: [...microRoutes.map(i => `/main/${i.path}`)],
});
// app.js
// 主应用创建共享状态:
const storeChange = async (props) => {
// 将主应用传递参数存储
actions.setActions(props);
// 微应用通过 props 获取共享状态并监听
props.onGlobalStateChange &&
(await props.onGlobalStateChange(async (values) => {
// 关掉的 tab,解除状态保持
const { delMicroUrls } = values;
// 遍历解除关闭页面的缓存
delMicroUrls.map(i => {
dropByCacheKey(i)
})
// dva 状态管理
const timer = setTimeout(() => {
getDvaApp()._store.dispatch({
type: 'global/updateMainParams',
payload: values,
});
clearTimeout(timer);
}, 10);
}, true));
};
export const qiankun = {
// 应用加载之前
async bootstrap(props) {
console.log('app bootstrap', props);
},
// 应用 render 之前触发
async mount(props) {
console.log('app mount', props);
await storeChange(props);
},
// 应用卸载之后触发
async unmount(props) {
console.log('app unmount', props);
props.offGlobalStateChange && props.offGlobalStateChange();
},
};
// layout
// import React, { useEffect } from 'react';
import {
connect,
KeepAliveLayout,
} from 'umi';
import { Layout, ConfigProvider } from 'antd';
import themeProviderHoc from '@/common/hocs/themeProviderHoc/index';
const { Content } = Layout;
const BasicLayout = (props) => {
const { children = <></>, ...rest } = props;
return (
<>
<ConfigProvider>
<KeepAliveLayout {...props}>
<Content>{React.cloneElement(children, rest)}</Content>
</KeepAliveLayout>
</ConfigProvider>
</>
);
};
export default connect((state) => {
const global = state.global;
return {
global,
};
})(
themeProviderHoc(BasicLayout, {
components: {
Menu: {
colorPrimary: '#BC261A',
},
},
token: {
colorPrimary: '#BC261A',
},
}),
);
本地代理
子应用在本地开发的时候,代理写在主应用里面这样才能生效,或者子应用的请求路径写成完整的绝对路径,例:127.0.0.1/api
部署
建议部署的主应用和子应用的路由形式保持一致,hash 对hash,history 对 history,这样从主应用进入到子应用页面路由可以保持一致。若是子应用部署前面带有路径 127.0.0.1/micro/,则需要在 umi.js 文件里加上对应的 base 以及 publicPath。