简介
qiankun 是基于 single-SPA 二次封装的微前端框架,single-SPA 的原理是基于路由的变化在注册的节点挂载资源,并实现子应用的生命周期,而 qiankun 在此基础上还加入了样式隔离,JS 沙箱,资源预载等功能,且提供了 umijs/plugin-qiankun 插件供 umi 更便捷地部署微前端。
使用的技术栈为React+Umi+qiankun,由于qiankun的文档不够完善,踩了很多坑,以此篇记录一下Umi-qiankun微应用的基本构建流程。
配置基本流程
React+Umi项目构建
参考Umi官方文档:v3.umijs.org/zh-CN/docs/…
添加乾坤依赖
yarn add @umijs/plugin-qiankun -D
子应用注册
子应用的注册是在主应用中完成的,分为静态配置和动态配置两种方式
在.umirc.ts 中静态配置子应用
# .umirc.ts
export default defineConfig({
title: 'MyProject',
qiankun: {
master: {
apps: [
{
name: 'myApp', // 微应用名称
entry: '//localhost:8057', // 微应用地址
},
],
},
},
})
在 app.ts 中运行时动态配置子路由
除了静态配置注册子应用,还可以在app.ts运行时配置中,请求接口获取配置数据,使得子应用的注册变得可根据接口动态配置,例如:
# app.ts
export const qiankun = fetch('/config').then(({ apps }) => ({
// 子应用配置
apps,
// 生命周期函数
lifeCycles: {
afterMount: (props) => {
console.log(props);
},
},
}));
子应用加载
子应用注册完之后就可以页面中使用他们了,子应用的加载同样有基于路由加载和<MicroApp>组件化加载两种方式
基于路由加载
通过配置式路由的方式指定子应用加载时机,在访问对应路由时,自动加载子应用并执行其生命周期函数
routes: [
{ path: '/microApp', microApp: 'myApp' },
],
基于组件化加载
引入<MicroApp>组件,通过 name 属性把注册过的子组件挂载到自身,需要注意的时此时子组件的路由必须和父组件匹配,因为子组件也会按照父组件当前路由去寻址加载自身组件。
import { MicroApp } from 'umi';
export function MyPage() {
return (
<div>
<div>
<MicroApp name="myApp" />
</div>
</div>
)
}
子应用配置
使用 qiankun 时无需在子应用中做过多的配置,主要是对子组件的生命周期进行控制
基本配置
# .umirc.ts
qiankun: {
slave: {},
},
在 package.json 中删除private:true字段,添加name字段,子应用配置完毕。
生命周期
在 app.ts 中引入 qiankun 配置子组件生命周期
# app.ts
export const qiankun = {
// 应用加载之前
async bootstrap(props: any) {
console.log('初始化', props);
},
// 应用 render 之前触发
async mount(props: any) {
console.log('已挂载', props);
},
// 应用卸载之后触发
async unmount(props: any) {
console.log('已卸载', props);
},
};
至此一个简单的微前端架构就构建好了,实际项目使用中除了以上基本内容外,还需要考虑到主应用和子应用之间的通讯,以及错误处理等操作。
主应用及子应用间通讯
在 Umi 同样提供了两种种方式供父子应用通讯,分别是:
- 配合
useModel使用 - 基于 Props 传递
使用 useModel 来传递数据
useModel是 Umi 基于 hooks 范式提供的简易全局数据管理方案,不仅能全局管理状态,还能像 hooks 一样全局复用逻辑,在 Umi-qiankun 中推荐采用这种方式来进行父子组件通讯,其分为主应用传数据和子应用拿数据两部分:
主应用传递数据:
使用<MicroApp>时直接通过组件 props 传递,例如:
import { MicroApp } from 'umi';
const MainApp = (props) => {
// 待传递的数据
const [myData, setMyData] = useState({
name:'主应用数据'
});
return (
<div>
<MicroApp name="myMicroApp" myData={myData} />
</div>
);
};
当使用路由加载子应用时,传递数据需要到app.ts中设置全局 Model 供子应用消费,如下:
# app.ts
export function useQiankunStateForSlave() {
const [masterState, setMasterState] = useState({
name: '全局数据数据',
});
// 全局数据
return {
masterState,
setMasterState,
};
}
子应用取得数据:
通过上面两种方式,数据已经传递到了子应用中,现在我们需要到子应用中取得数据:
首先是使用 useModel 拿取数据:
import { useModel } from 'umi';
const MyMicroApp = (props)=>{
// 取得数据
const masterProps = useModel('@@qiankunStateFromMaster');
console.log('子应用拿到的数据', masterProps)
return <div>...</div>
}
此外还可以使用 umi 提供的高阶组件connectMaster更方便的拿取数据:
import { connectMaster } from 'umi';
const MyMicroApp = (props) => {
console.log('数据透传到了props中', props);
return <div>...</div>;
};
export default connectMaster(MyMicroApp);
基于 Props 传递数据
使用这种方法主应用需要到umirc.ts中配置式地传递数据,例如:
# umirc.ts
export default defineConfig({
title: 'mainApp',
qiankun: {
master: {
apps: [
{
name: 'microApp',
entry: '//localhost:8888',
props: {
name: '你要传递的数据',
descript:'通过props以对象的方式传递',
},
},
],
},
},
})
同时也可以在运行时配置app.ts中传递数据:
# app.ts
export const qiankun = fetch('/config').then((config) => {
return {
apps: [
{
name: 'mdShow', // 微应用名称
entry: '//localhost:8057', // 微应用入口文件
props: {
name: '你要传递的数据',
descript:'通过props以对象的方式传递',
testFun:(data:any)=>{
console.log('此方式可以传递函数',data)
}
},
},
],
};
});
在子应用消费数据时,只能到其 app.ts 中从生命周期函数获取数据:
# app.ts
export const qiankun = {
// 应用加载之前
async bootstrap(props: any) {
console.log('子应用初始化', props);
},
// 应用 render 之前触发
async mount(props: any) {
console.log('子应用已挂载', props);
},
// 应用卸载之后触发
async unmount(props: any) {
console.log('子应用已卸载', props);
},
};
此时子应用生命周期函数拿到的 props ,即包含了主应用传递的数据。
可以看到次方法传值不够灵活,是配置化传值且需要到生命周期函数中进行消费,仅特定场景可以发挥作用。且在使用时还有以下注意的点:
- 不要以 name 作为属性名,因为默认接收的 props 中包含了主应用中注册时声明的 name 字段,会被覆盖
- 主应用在 umirc.ts 中配置传递参数时,无法传递函数
所以在使用时建议采取 useModel 的方式进行父子应用通讯,采用useState和useModel方案即可实现父子应用及兄弟应用间的通讯
错误处理
Umi-qiankun 的错误处理,一般是在子应用加载失败,或者子应用中出现不可预知的错误时进行错误处理,所以是在主应用中进行的。qiankun 提供了 single-SPA 中的addErrorHandler和自封装的addGlobalUncaughtErrorHandler来进行错误处理,可以在app.ts中配置:
import * as qiankun from 'qiankun';
import { history } from 'umi';
qiankun.addErrorHandler((err) => {
console.log('捕获错误', err);
});
qiankun.addGlobalUncaughtErrorHandler((err) => {
history.push('/404');
console.log('捕获未知错误', err);
});
export { qiankun };
踩坑记录
- qiankun 会一次性加载所有的子应用,如果有子应用不需要使用时应当清除注册,否则会产生报错(必须是清除注册)
- 使用 MicroApp 组件化引入的时候,子应用路由应该和主应用保持一致