探索微前端的場景極限
點擊上方“前端壹棧“,回復“1”進交流群
加入我們一起學習,天天進步
本文由螞蟻金服體驗技術(shù)部的 kuitos 授權(quán)轉(zhuǎn)發(fā),文中主要介紹總結(jié)了一些基于 qiankun 的微前端應用場景與實踐,讓我們跟隨作者一起來領(lǐng)略微前端的魅力~
基礎(chǔ)場景
與路由綁定的方式渲染微應用
通常情況下,我們接觸的最多的微前端的實踐,是以 URL/路由 為維度來劃分我們的微應用,以 OneX 平臺(螞蟻金融云基于微前端架構(gòu)打造的統(tǒng)一接入平臺)為例:

接入這類平臺的微應用,通常只需要提供自己的 entry html 地址,并為其分配一個路由規(guī)則即可。
這背后的實現(xiàn)則是基于 qiankun 的 registerMicroApps API,如:
import { registerMicroApps } from 'qiankun';
registerMicroApps([
{
name: 'app1',
container: '#container',
entry: '//micro-app.alipay.com/',
activeRule: '/app1'
}
])
路由與應用綁定的方式簡單直觀,是微前端中最為常見的使用場景,通常我們會用這種方式將一堆獨立域名訪問的 MPA 應用,整合成一個一體化的 SPA 應用。
但這類場景也有自己的局限性:
由于URL/路由的 唯一性/排他性 的特點,這種方式只適用單實例場景需求 微應用的調(diào)度都是由路由系統(tǒng)來自動處理的,雖然省事但是碰到更復雜的需求,如同一個路由下,根據(jù)不同的用戶權(quán)限展示不同的微應用這類個性化訴求,需要寫一些中間層代碼來曲線救國 應用掛載的容器節(jié)點等需提前準備好,不然碰到 動態(tài)/嵌套 路由這類情況,可能會因為路由 listener 監(jiān)聽觸發(fā)的時序不確定,導致微應用無法完成掛載的問題
以組件的方式使用微應用
qiankun 2.0 的發(fā)布帶來一個全新的 API loadMicroApp,通過這個 API 我們可以自己去控制一個微應用加載/卸載,這個方式也是 qiankun 2.0 的重磅特性:
import { loadMicroApp } from 'qiankun';
// do something
const container = document.createElement('div');
const microApp = loadMicroApp({ name: 'app', container, entry: '//micro-app.alipay.com' });
// do something and then unmount app
microApp.unmout();
// do something and then remount app
microApp.mount();
開發(fā)者可以在脫離路由的限制下,以更自由的方式去渲染我們的微應用?;?loadMicroApp API,我們只需要做一些簡單的封裝,即可以類似組件的開發(fā)體驗,完成微應用的接入,以 React 為例:
第一步:封裝一個 MicroApp 組件:
import { loadMicroApp } from 'qiankun';
import React from 'react';
export default class MicroApp extends React.Component {
containerRef = React.createRef();
microApp = null;
componentDidMount() {
const { name, entry, ...props } = this.props;
this.microApp = loadMicroApp(
{ name, entry, container: this.containerRef.current, props },
);
}
componentWillUnmount() {
this.microApp.unmount();
}
componentDidUpdate() {
const { name, entry, ...props } = this.props;
this.microApp.update(props);
}
render() {
return <div ref={this.containerRef}></div>;
}
}
第二步:通過 MicroApp 組件引入微應用:
import MicroApp from './MicroApp';
import React from 'react';
class App extends React.Component {
render() {
return (
{
this.props.admin
? <MicroApp name="admin" entry="http://localhost:8080/" level={10} />
: <MicroApp name="guest" entry="http://localhost:8081/" level={1} />
}
)
}
}
如果你是 umi 應用,那只需要直接使用插件封裝好的組件即可:
import { MicroApp } from 'umi';
function MyComponent() {
return (
<div>
<MicroApp name="qiankun" age={1.5} stars={8700} />
</div>
)
}
這類方式適用于一些可共用的、帶業(yè)務邏輯的服務型組件(類似于我們以前常說的端對端組件):比如帶聊天交互的客服機器人、帶引導功能的 intro 服務等。
如螞蟻 sofa 產(chǎn)品控制臺中的這個使用入門微應用:
右側(cè)呼出的窗口即為一個獨立開發(fā)、獨立發(fā)布的微應用
通過組件的這種方式,我們可以完全自主的控制微應用的渲染,并與之做一些復雜的交互。不論是在開發(fā)者的編碼心智,還是用戶的體驗上,都跟使用一個普通的業(yè)務組件無異。
組件的方式非常靈活,幾乎解決了所有路由綁定方式渲染微應用的問題,但也有自己的一些局限:比如我們會要求這類微應用必須是不帶路由系統(tǒng)的 widget 類型,不然也會出現(xiàn)多實例時路由沖突的問題。
嵌套渲染場景
有一些更復雜的場景中,我們可能需要使用到「套娃」的方式集成我們的若干微應用。
比如我們要在應用 A 下集成應用 B 的商品列表頁,然后在應用 B 的商品列表頁呼出應用 C 的買家詳情頁,所有應用的喚起都以 彈層/抽屜 這種不刷新的交互來完成。
在有了上面兩個場景的實踐經(jīng)驗后,我們很容易得出這樣的組合邏輯:
在應用 A 中通過調(diào)用 loadMicroApp(B) 的方式喚起微應用 B,然后在微應用 B 中通過 loadMicroApp(C) 的方式喚起微應用 C,通過這樣的調(diào)用鏈路即可很完美的完成產(chǎn)品上的訴求。
但是現(xiàn)實情況往往沒有那么簡單,前面提到過,若想要 loadMicroApp API 能符合預期的運行,我們需要確保被加載的微應用是不含自己的路由系統(tǒng),否則會出現(xiàn)多個應用間路由系統(tǒng)互相 搶占/沖突 的情況。
而現(xiàn)實情況是,我們大部分需要復用的頁面/組件,都會是某個站點的局部路由頁,很少有人會專門起一個倉庫,用來專門把這個頁面抽取成一個微應用,比如上面提到的買家詳情頁。
這種場景下,我們其實只需要確保微應用的路由系統(tǒng)不會干擾到全局的 URL 系統(tǒng)即可。幸運的是 react-router 的 memory history 模式很好的解決了這一問題。如果你是一個 umi 應用,只需要直接使用我們封裝好的組件即可完成 memory history 的運行時切換:
import { MicroAppWithMemoHistory } from 'umi';
<Drawer>
<MicroAppWithMemoHistory name="buyer" url='/buyers/123' />
</Drawer>
交互效果可以參考:
視頻中 app1/app2 均是子應用,但是在 app1 中可以再通過抽屜呼出 app2,同時瀏覽器地址欄也不會被 app2 的路由干擾。
關(guān)于嵌套渲染相關(guān)的詳細介紹,可以看這篇《基于微前端的大型中臺項目融合方案》
極限渲染場景
如果你覺得嵌套微應用就是我們場景的天花板了,那未免有點小看群眾們的想象力了。
在我們內(nèi)部的一個設(shè)計工程化平臺里,我們通過 qiankun 成功的把一個微應用的 20+ 路由頁同時渲染到了一個 url 下:

上圖中右側(cè)列表中每一個 demo 都是通過 qiankun 渲染的一個獨立微應用實例,而這里面每一個微應用實例,實際對應是同一個 react 應用的不同路由頁。
不同于前面幾個場景,將同一個應用的不同頁面,同時渲染到主應用的不同 UI 容器中這個需求下,有幾個比較特殊的問題需要去考慮:
是否需要特殊的微應用生產(chǎn)方式 多路由系統(tǒng)共存帶來的 沖突/搶占 問題 不同微應用間的樣式隔離 如何優(yōu)化渲染性能:既然每一個微應用實例實際對應的是同一個應用,那我們?nèi)绾伪M可能多的復用一些運行時或者沙箱,從而降低這么多微應用同時渲染代理的運行時開銷
篇幅考慮,針對這類場景優(yōu)化的技術(shù)細節(jié)不在這里介紹了,后面會單獨寫一篇來介紹
解決了這些問題后,我們只需要在我們的項目中這么去使用就可以了:
// MicroAppWithMemoHistory 是基于 memory history 封裝的微應用加載器
import { MicroAppWithMemoHistory } from 'umi';
function Home {
return (
<div>
{ /* 將同一個應用的不同路由頁同時渲染出來 */ }
<MicroAppWithMemoHistory name="demo" url="/demo1" />
<MicroAppWithMemoHistory name="demo" url="/demo2" />
<MicroAppWithMemoHistory name="demo" url="/demo3" />
<MicroAppWithMemoHistory name="demo" url="/demo4" />
<MicroAppWithMemoHistory name="demo" url="/demo5" />
<MicroAppWithMemoHistory name="demo" url="/demo6" />
</div>
)
}
更多的想象空間
工程上的想象空間
微前端架構(gòu)除了其帶來的巨石應用解構(gòu)、技術(shù)棧無關(guān)等工程能力外,也為我們對一些已有的工程問題帶來了新的解題思路,比如:
npm 包分發(fā)業(yè)務組件背后的工程問題
在以前,我們經(jīng)常通過發(fā)布 npm 包的方式復用/共享我們的業(yè)務組件,但這種方式存在幾個明顯的問題:
npm 包的更新下發(fā)需要依賴產(chǎn)品重新部署才會生效 時間一長就容易出現(xiàn)依賴產(chǎn)品版本割裂導致的體驗不一致 無法灰度 技術(shù)棧耦合
說白了就是 npm 包這種靜態(tài)的共享方式,喪失了動態(tài)下發(fā)代碼的能力,導致了其過慢的工程響應速度,這在現(xiàn)在云服務流行的時代就會顯得格外扎眼。而微前端這種純動態(tài)的服務依賴方式,恰好能方便的解決上面的問題:被依賴的微應用更新后的產(chǎn)物,在產(chǎn)品刷新后即可動態(tài)的獲取到,如果我們在微應用加載器中再輔以灰度邏輯,那么動態(tài)更新帶來的變更風險也能得到有效的控制。
新的 UI 共享模式
在以前,如果我們希望復用一個站點的局部 UI,幾乎只有這樣一條路徑:從業(yè)務系統(tǒng)中抽取出一個組件 -> 發(fā)布 npm 包 -> 調(diào)用方使用 npm 包。
且不說前面提到的 npm 自身的問題,單單是從一個已有的業(yè)務系統(tǒng)中抽取出一個 UI 組件這個事情,都可能夠我們喝一壺的了。我們不僅要在物理層面,將這部分代碼抽取成若干個單獨的文件,同時還要考慮如何跟已有的系統(tǒng)做上下文解耦,這類工作出現(xiàn)在一個越是年代久遠的項目上,實施起來就越是困難,做過這類事情的同學應該都會深有體會。
不同于組件庫的研發(fā)流程,微前端的場景下,大部分時候我們不會為了去復用一個 UI,而去專門寫一個微應用出來。通常我們期望的是,從一個已有系統(tǒng)中,直接選取我們需要復用的部分,嵌入到我們自己的容器里進行渲染。
基于上面提到過的微應用多實例的渲染方案,我們可以考慮將需要復用的組件,以路由 URL 作為 ID 的方式導出。比如我們有這樣一個 A 應用有一個這樣的頁面:
function OnePage() {
return (
<div>
<SearchForm/>
<UserList/>
</div>
)
}
我們有另外一個應用,希望單獨復用 A 應用的用戶列表部分的交互跟 UI,那我們只需要多加一條路由規(guī)則:
<Switch>
...
<Route path="/userList" memory={true}>
<UserList/>
</Route>
</Switch>
依賴方只需要配合 memory history 并指定 url 為 /userList 即可完成渲染(參考上面嵌套渲染章節(jié))。
站點即配置
當我們將所有可以共享的服務單元變成一個個獨立的微應用之后,我們便可以通過配置的方式描述我們的站點了,類似:
{
layout: 'admin-pro',
apps: [
{ name: 'abc', props: {} },
{ name: 'bcd', props: {} },
{ name: 'cde', props: {} },
]
}
其中 layout 可以是一個帶基礎(chǔ)交互框架、用戶鑒權(quán)等公共能力的微應用,也可以是一組微應用的集合(類似 babel 中的 preset 插件),而 apps 則是一組需要在當前站點渲染出來的業(yè)務微應用。
這種方式非常適用于,當我們要在一個業(yè)務域下開發(fā)多個細分服務站點時,通過這種配置的方式,配合一個運行時的微應用編排引擎,快速的生成一系列視覺一致、交互統(tǒng)一的業(yè)務站點出來。
產(chǎn)品上的想象空間
在有了上面那些場景背后的技術(shù)支撐后,在產(chǎn)品上,我們就已經(jīng)多出很多想象空間了。
但我們還可以想的更極限一點:
比如我們知道微信有一個公眾號浮窗的功能,包括安卓系統(tǒng)常見的小窗模式,解決的就是空間獨占、以及跨空間時的交互問題。

那我們在中后臺也可以參考類似的設(shè)計,將不同空間的關(guān)聯(lián)性操作以這種非獨占的形態(tài)聚合到一起,從而降低流程上的斷層感,提升產(chǎn)品體驗。
demo.jpg
(這里本有一張螞蟻內(nèi)部系統(tǒng)的交互示意圖,因涉及保密信息無法公開,有興趣的同學可以選擇加入我們后做進一步的交流分享??)
寫在最后
微前端提供的這些漸進式更新、動態(tài)組合、 服務拓展的能力,相信大家通過我們介紹的這些常見場景,以及一些極致條件下的解決方案中能窺其一二。
但需要強調(diào)的是,任何技術(shù)架構(gòu)都不可能是銀彈,我們不必對微前端過于神化/劣化。本篇僅希望在分享了我們在微前端領(lǐng)域的一系列探索之后,能給其他開發(fā)者帶來一些新的選擇和啟發(fā),從而為其工程及產(chǎn)品上帶來更多的可能性。
最后的最后,誠招天下英雄
上面提到的這些案例,包括一些想象中的場景,正在我們團隊內(nèi)有條不紊地發(fā)生著,只是時機未到還不能更詳細的對外宣傳并與大家分享,歡迎有興趣的同學可以加入我們,與我們一起去探索更多微前端的可能性。
如果您對微前端感興趣,請發(fā)簡歷到 [email protected],我們非常期望有機會能與您共事,探索出微前端下更多的場景可能性。
如果您對微前端沒什么興趣,不要緊,只需要您對 antd、AntV、dva、umi、eggjs、ahooks 中任一類別的領(lǐng)域感興趣,您也可以發(fā)簡歷到 [email protected] 來與我們共商大事。
團隊介紹
大部門:螞蟻集團體驗技術(shù)部 部門主管:玉伯 部門開源作品:antd、AntV、dva、umi、eggjs、qiankun、ahooks 等 部門商業(yè)化產(chǎn)品:語雀 直屬部門:平臺前端技術(shù)部 - 部門主管:偏右(antd 負責人),部門介紹:https://www.yuque.com/afx/about/skyline 要求:基本沒要求,p5~p8 我們都需要,只要您想來試試就可以投簡歷到 [email protected],HC 非常多?。?/section> base 地:杭州、上海、成都 三地任選
關(guān)注我們
我們將為你帶來最前沿的前端資訊。
