本文中的微前端基於 qiankun 框架
多個子應用共存
如果需要多個子應用同時共存,在管理就有很多例子:
https://qiankun.umijs.org/zh/faq#%E5%A6%82%E4%BD%95%E5%90%8C%...
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 微前端大火的情況下,後續我將調研此方案,應該能給出更好的解決辦法;