前言
随时umi4的到来, 我把qiankun学习计划也提上了日程, 在系统的学习一周后, 下面通过umi4+qiankun+react 展开说说我的学习成果吧~
说明
鉴于版面关系, 代码量较多, 只展示关键代码, 建议直接阅读DEMO项目更佳, 请移步至DMEO仓库
后续有个延伸阅读, <<Qiankun避坑指南>>, 本文反响不错的话, 将后续奉上 !!!
项目结构
- 主应用(基于umi4构建)
- 微应用(基于umi3构建)
- 微应用2(基于react-create-app构建)
- 微应用3(react-create-app + router构建)
项目构建请参考官网提供的示例(很全面), 这里不做展示, 链接放在底部
主要完成以下功能:
挂载
- 基于qiankun提供的loadMicroApp方法
/* main/src/pages/load.tsx 关键代码 */
let microApp: any = null;
const LoadMicroApp: React.FC = () => {
const containerRef = useRef(null)
useEffect(() => {
if (containerRef.current) {
microApp = loadMicroApp({
name: 'sub-app-3',
entry: '//localhost:5003',
container: containerRef.current,
props: {
base: '/',
message: 'hello sub-app-3, load as loadMicroApp function',
callback: (message: string) => callback(message, 3)
},
}, {
// @ts-ignore
fetch: customFetch
});
}
return () => {
microApp?.unmount()
microApp = null
}
}, [containerRef.current])
return (
<div style={{border: 'solid 1px #cecece'}} ref={containerRef}/>
);
};
- 基于umi-plugin-qiankun提供的MicroApp , MicroAppWithMemoHistory 组件
/* main/src/pages/load/index.tsx 关键代码 */
<MicroApp name="sub-app-2" />
/* sub-app-1/src/pages/two/index.tsx 关键代码 */
<MicroAppWithMemoHistory
name="sub-app-3"
base={window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ ? '/sub-app-1' : '/'}/>
- 基于umi-plugin-qiankun提供的路由声明形式绑定挂载
/* .umirc.ts 关键代码 */
routes: [
...
{
name: 'react-router-micro-app',
path: '/sub-app-3',
microApp: 'sub-app-3',
icon: 'SmileOutlined',
routes: [
{
name: '嵌套路由1',
path: '/sub-app-3/one',
},
{
name: '嵌套路由2',
path: '/sub-app-3/three',
},
{
path: '/sub-app-3',
redirect: '/sub-app-3/one',
},
]
},
]
- 微应用嵌套微应用挂载
/* sub-app-1/src/app.ts 关键代码 */
export qiankun = {
master: {
// 微应用运行时需要再注册一次, .umirc.ts声明也需要, 否则出错
apps: [
{
name: 'sub-app-2',
entry: '//localhost:5002',
},
{
name: 'sub-app-3',
entry: '//localhost:5003',
},
],
prefetch: false,
},
// 应用加载之前
async bootstrap(props: any) {
setCreateHistoryOptions({basename: props?.base || '/'})
},
}
/* .umirc.ts 关键代码 */
routes: [
...
{
name: 'umi3-micro-app',
path: '/sub-app-1',
layout: true,
microApp: 'sub-app-1',
icon: 'SmileOutlined',
routes: [
{
name: '应用间通信',
path: '/sub-app-1/one',
},
{
name: '应用间嵌套',
path: '/sub-app-1/two',
},
{
name: '应用间通信',
path: '/sub-app-1/sub-app-3',
routes: [
{
name: '嵌套路由1',
path: '/sub-app-1/sub-app-3/one',
},
{
name: '嵌套路由2',
path: '/sub-app-1/sub-app-3/three',
},
]
}
]
},
]
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'sub-app-2',
entry: '//localhost:5002',
},
{
name: 'sub-app-3',
entry: '//localhost:5003',
},
],
},
slave: {}, //微应用必须配置
},
通信
- 基于qiankun-apps注册时的props属性透传
/* main/src/app.tsx 关键代码 */
export const qiankun: any = {
apps: [
{
name: 'sub-app-1',
entry: '//localhost:5001',
activeRule: '/sub-app-1',
container: '#micro-app-1',
props: {
autoCaptureError: true,
base: '/sub-app-1',
defaultProps: {
slogan: 'Hello MicroFrontend from qiankun-apps-props',
callback
}
},
},
...
]
- 基于qiankun提供的全局状态-initGlobalState
主应用
/* main/src/app.tsx 关键代码 */
const state = {
slogan: 'Hello MicroFrontend from qiankun-initGlobalState',
callback
}
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
// 监听数据变化
actions.onGlobalStateChange((state, prev) => {
// update
//actions.setGlobalState(state);
});
// 取消监听
// actions.offGlobalStateChange();
子应用
/* sub-app-1/src/app.ts 关键代码 */
const qiankun = {
...
// 应用 render 之前触发
async mount(props: any) {
// 监听qiankun initState
props.onGlobalStateChange((state: any, prev: any) => {
qiankun_state = state
// 简单实现个订阅
listener?.(state, prev)
})
qiankun_props = props
},
}
- 基于umi-plugin-qiankun提供的hooks-useQiankunStateForSlave
- umi微应用使用hooks-useModel, hoc-connectMaster方式获取内容
主应用
/* main/src/app.tsx 关键代码 */
export function useQiankunStateForSlave() {
return {
slogan: 'Hello MicroFrontend from umi-useQiankunStateForSlave',
callback
};
}
子应用
/* sub-app-1/src/pages/one.index.tsx 关键代码 */
import {connectMaster, useModel} from "umi";
const Sub = connectMaster(Record)
export default function One(props: any) {
const globalProps = useModel('@@qiankunStateFromMaster');
...
}
样式隔离
- 基于qiankun提供的sandbox-experimentalStyleIsolation实现样式隔离
/* main/src/pages/theme/index.ts 关键代码 */
const defaultParams = {
base: '/',
url: '/theme',
settings: {
sandbox: {
experimentalStyleIsolation: true
}
}
}
...
export default function Theme() {
return (
<PageContainer
ghost
title={"基于sandbox: {experimentalStyleIsolation: true}样式隔离"}
>
<pre style={{background: '#f0f0f0', padding: '0 24px'}}>{text}</pre>
<Divider/>
<h3>主应用(main)</h3>
<div style={{background: '#f0f0f0', padding: 24, display: "flex", flexDirection: "column"}}>
<h4>antd-组件库样式</h4>
<Space>
<Button type={"primary"}>主应用按钮1</Button>
<Button>主应用按钮2</Button>
</Space>
<Space style={{marginTop: 16}} size={24}>
<div>
<h4>CSS Modules</h4>
<a className={styles.link}>我是Link</a>
</div>
<div>
<h4>内联样式</h4>
<a style={{color: '#2572E6'}}>我是Link</a>
</div>
<div>
<h4>外联样式</h4>
<a className={"link"}>我是Link</a>
</div>
<div>
<h4>默认</h4>
<a>我是Link</a>
</div>
</Space>
</div>
<Divider/>
<h3>微应用(sub-app-1)</h3>
<MicroApp name="sub-app-1" {...defaultParams} />
<Divider/>
<h3>微应用(sub-app-3)</h3>
<MicroApp name="sub-app-3" {...defaultParams} />
</PageContainer>
)
}
- 基于css-loader添加前缀
主应用
/* main/.umirc.ts 关键代码 */
export default defineConfig({
antd: {
configProvider: {
prefixCls: 'mainAnt',
},
},
lessLoader: {
modifyVars: {
'@ant-prefix': 'mainAnt',
"primary-color": "#004FD9"
},
javascriptEnabled: true,
},
mfsu: false
});
子应用
/* sub-app-3/craco.config.js 关键代码 */
const {name} = require('./package.json');
const CracoAntDesignPlugin = require("craco-antd");
module.exports = {
plugins: [{
plugin: CracoAntDesignPlugin,
options: {
customizeTheme: {
"@primary-color": "#1DA57A",
},
}
}],
...
}
错误处理
- 基于qiankun提供的autoCaptureError属性开启组件异常捕获
/* main/src/pages/theme/index.tsx */
const defaultParams = {
base: '/',
url: '/theme',
settings: {
sandbox: {
experimentalStyleIsolation: true
}
},
// 自动捕获错误, 吊起ant <Result /> (这里并没有触发, 有BUG)
autoCaptureError: true
}
- 基于qiankun提供的errorBoundary属性实现自定义异常页面
路由形式
/* main/src/app.tsx 关键代码 */
export function patchClientRoutes({routes}: any) {
routes[0].children.forEach((item: any, index: number) => {
if (item.microApp) {
console.log("item", item)
routes[0].children[index].element = getMicroAppRouteComponent({
appName: item.microApp,
base: item.microAppProps?.base || '/',
routePath: item.path,
masterHistoryType: item.microAppProps?.hisory || 'browser',
routeProps: {
errorBoundary: (error: any) => <CustomErrorBoundary error={error}/>
}
})()
}
})
}
组件形式
/* main/src/pages/theme/index.tsx */
const defaultParams = {
base: '/',
url: '/theme',
settings: {
sandbox: {
experimentalStyleIsolation: true
}
},
// 自定义异常页面
errorBoundary: (error: any) => <CustomErrorBoundary error={error}/>
}
- 基于qiankun提供的addGlobalUncaughtErrorHandler全局异常捕获
/* main/src/app.tsx 关键代码 */
// 捕获全局微应用错误
addGlobalUncaughtErrorHandler((event) => {
// 这里会频繁触发, 注意使用
})
环境准备
clone
git clone https://github.com/xoptimal/qiankun-demo.git
项目使用pnpm管理依赖, 请务先安装pnpm
npm install -g pnpm
安装依赖
pnpm i
启动项目
pnpm dev
http://localhost:5000
预览
主应用
微应用页面形式挂载
微应用路由形式挂载
应用间通信
微应用嵌套微应用挂载
微应用嵌套微应用路由形式挂载
样式隔离
微应用路由形式加载自定义错误页面
微应用组件形式加载自定义错误页面
参考链接
最后
欢迎交流, 如果本项目对你有帮助的话, 欢迎━(`∀´)ノ亻! ⭐⭐⭐ ~