博客 / 詳情

返回

qiankun 中遇見的問題集合

本文中的微前端基於 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 微前端大火的情況下,後續我將調研此方案,應該能給出更好的解決辦法;

user avatar dujing_5b7edb9db0b1c 頭像 flymon 頭像 sunhengzhe 頭像 suporka 頭像 lesini 頭像 user_p5fejtxs 頭像 william_wang_5f4c69a02c77b 頭像 tempest_619c7f9d4e321 頭像 amsterdam_5caf807441f49 頭像 ran_agppr 頭像 yookoo 頭像 tyhan 頭像
25 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.