qiankun 中遇见的问题集合
本文中的微前端基于 qiankun 框架
多个子应用共存
如果需要多个子应用同时共存,在管理就有很多例子:
registerMicroApps([
// 自定义 activeRule
{ name: 'reactApp', entry: '//localhost:7100', container, activeRule: () => isReactApp() },
{ name: 'react15App', entry: '//localhost:7102', container, activeRule: () => isReactApp() },
{ name: 'vueApp', entry: '//localhost:7101', container, activeRule: () => isVueApp() },
]);
start({ singular: false });
而使用 loadMicroApp 也会更加灵活:
class App extends React.Component {
containerRef = React.createRef();
microApp = null;
componentDidMount() {
this.microApp = loadMicroApp({
name: 'app1',
entry: '//localhost:1234',
container: this.containerRef.current,
props: { brand: 'qiankun' },
});
}
componentWillUnmount() {
this.microApp.unmount();
}
componentDidUpdate() {
this.microApp.update({ name: 'kuitos' });
}
render() {
return <div ref={this.containerRef}></div>;
}
}
这里对于多个子应用同时存在的方案不再赘述,要解决的是多个子应用同时存在时,css的冲突问题:
解决方法 1:通用的挂载入口解决
比如你使用的是 antd 组件库, 在他的配置 provider 中有挂载节点的配置:
https://ant-design.antgroup.com/components/config-provider-cn
import React from 'react';
import { ConfigProvider } from 'antd';
// ...
const Demo: React.FC = () => (
<ConfigProvider direction="rtl" getPopupContainer={()=>{
// 通过判断 当前是否某个特殊环境,如微前端环境
// 返回不同的 dom
return document.getElementById('content-id')
// 或者返回默认
return document.body
}}>
<App />
</ConfigProvider>
);
export default Demo;
当然这种方法适用于只有 antd 组件的场景,一旦使用三方的组件,没有任何挂载 dom 的入口 API就会抓瞎
这个时候,如果你能改三方包则需要一个个检查和升级,那么有没有一个方法可以解决所有问题呢?
方案 2: 代理 API
通用型方案出现,通过代理 api 的操作来解决挂载问题
// 保存原始的 document.body.appendChild 方法
const originalAppendChild = document.body.appendChild;
// customDom 是子应用的根节点元素
const proxiedAppendChild = new Proxy(document.body.appendChild, {
apply(target, thisArg, argumentsList) {
// 在这里可以添加自定义逻辑
console.log('即将添加一个新元素到自定义DOM');
// 获取要添加的元素
const elementToAdd = argumentsList[0];
// 将元素添加到自定义DOM元素中
customDom.appendChild(elementToAdd);
// 如果需要在添加到自定义DOM后还有其他操作,可以在这里添加
return elementToAdd;
}
});
在切到到气筒页面,如主应用的某些页面,或其他技术站页面,再或者是 iframe 页面时,需要取消代理:
// 取消代理的函数
function cancelAppendChildProxy() {
if (document.body.appendChild === proxiedAppendChild) {
document.body.appendChild = originalAppendChild;
console.log('已成功取消对 document.body.appendChild 方法的代理');
} else {
console.log('当前 document.body.appendChild 方法未处于代理状态');
}
}
注意: 在代理 document.body.appendChild
的函数时,对应的 document.body.removeChild
也需要添加。
这里为了简化代码,方便展示,也去掉了很多特殊场景的判断。
如果需要考虑兼容性,可以不使用 Proxy, 使用最简单的赋值方法即可:
// 保存原始的 document.body.appendChild 方法
const originalAppendChild = document.body.appendChild;
// customDom 是子应用的根节点元素
const proxyAppendChild = (dom) => {
const target = cutomDom || document.body;
// 记得加上 try catch 避免报错
// 其他的一些特殊场景的判断
target[`appendChild`](dom);
}
document.body['appendChild'] = proxyAppendChild;
在这里还有个特殊场景需要注意:
16 升级到 17 后,React 将事件委托到 ReactDOM 挂载的根节点上,比如 div#app,而不再是原来 document。
所以在我们添加子应用渲染 dom 节点时,需要注意不要 appendChild
到更高一层的节点上,不然在点击 modal 组件时,里面的按钮都会失效
子应用保活
按照正常情况来说, 微前端应用应该不需要保活,但是形式比人强,有些客户、需求就必须要这样做。
主要的思路是挂载的 dom ,我这使用的是这个仓库:react-activation 来实现 keep alive
最终将 dom 挂载到不被 react-router
影响的页面上即可。
注意:即使页面不会显示,但是我们的应用里有 react-router
时,还会有对应的路由映射机制,某些特殊的路由判断需要去除,比如: 404 的判断等等
如何提取出公共的依赖库
官方不太推荐将运行时共用, 即使共用也只推荐使用了一个方案:external
, 在主应用中加载必要的公共依赖
在如今 MF 微前端大火的情况下,后续我将调研此方案,应该能给出更好的解决办法;