qiankun是阿里開源的一個(gè)微前端的框架,在阿里內(nèi)部已經(jīng)經(jīng)過一批線上應(yīng)用的充分檢驗(yàn)及打磨了,所以可以放心使用。qiankun有什么優(yōu)勢呢?
- 基于single-spa封裝的,提供了更加開箱即用的API
- 技術(shù)棧無關(guān),任意技術(shù)棧的應(yīng)用均可使用/接入,不論是 React/Vue/Angular/jQuery 還是其他等框架。
- HTMLEntry的方式接入,像使用iframe一樣簡單
- 實(shí)現(xiàn)了single-spa不具備的樣式隔離和is隔離
- 資源預(yù)加載,在瀏覽器空閑時(shí)間預(yù)加載未打開的微應(yīng)用資源,加速微應(yīng)用打開速度。
現(xiàn)有的微前端方案
iframe
iframe大家都很熟悉,通過iframe實(shí)現(xiàn)的話就是每個(gè)子應(yīng)用通過iframe標(biāo)簽來嵌入到父應(yīng)用中,iframe具有天然的隔離屬性,各個(gè)子應(yīng)用之間以及子應(yīng)用和父應(yīng)用之間都可以做到互不影響。
但是iframe也有很多缺點(diǎn):
- url不同步,如果刷新頁面,iframe中的頁面的路由會丟失。
- 全局上下文完全隔離,內(nèi)存變量不共享。
- 不同步,比如iframe中的頁面如果有帶遮罩層的彈窗組件,則遮罩就不能覆蓋整個(gè)瀏覽器,只能在iframe中生效。
- 慢。每次子應(yīng)用進(jìn)入都是一次瀏覽器上下文重建、資源重新加載的過程。
single-spa
single-spa是最早的微前端框架,可以兼容很多技術(shù)棧。
single-spa首先在基座中注冊所有子應(yīng)用的路由,當(dāng)URL改變時(shí)就會去進(jìn)行匹配,匹配到哪個(gè)子應(yīng)用就會去加載對應(yīng)的那個(gè)子應(yīng)用。
相對于iframe的實(shí)現(xiàn)方案,single-spa中基座和各個(gè)子應(yīng)用之間共享著一個(gè)全局上下文,并且不存在URL不同步和UI不同步的情況,但是single-spa也有以下的缺點(diǎn):
- 沒有實(shí)現(xiàn)js隔離和css隔離
- 需要修改大量的配置,包括基座和子應(yīng)用的,不能開箱即用
核心概念
- 主應(yīng)用(基座):整個(gè)微前端應(yīng)用的入口,負(fù)責(zé)加載和管理子應(yīng)用。
- 子應(yīng)用:獨(dú)立的前端應(yīng)用,可以獨(dú)立開發(fā)、獨(dú)立部署、獨(dú)立運(yùn)行。
- 生命周期:主應(yīng)用和子應(yīng)用之間的生命周期鉤子,用于控制應(yīng)用的加載、啟動、卸載等過程。
- 沙箱:用于隔離子應(yīng)用的 JavaScript 執(zhí)行環(huán)境,防止子應(yīng)用之間的沖突和污染。
- 應(yīng)用間通信:主應(yīng)用和子應(yīng)用之間的通信機(jī)制,用于實(shí)現(xiàn)數(shù)據(jù)共享和事件傳遞。
qiankun 微前端的基本使用流程是什么?
- 在主應(yīng)用中安裝 qiankun 庫,并注冊需要加載的子應(yīng)用。
- 在子應(yīng)用中導(dǎo)出一個(gè)生命周期對象,并在主應(yīng)用中注冊該子應(yīng)用。
- 在主應(yīng)用中啟動 qiankun 應(yīng)用,并指定需要加載的子應(yīng)用。
- 在主應(yīng)用中渲染子應(yīng)用的容器,并在容器中加載子應(yīng)用。
- 在主應(yīng)用和子應(yīng)用中實(shí)現(xiàn)應(yīng)用間通信和數(shù)據(jù)共享。
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)按需加載?
qiankun 微前端的子應(yīng)用可以通過導(dǎo)出一個(gè)異步加載函數(shù)來實(shí)現(xiàn)按需加載,例如:
export async function bootstrap() {
// 子應(yīng)用的啟動邏輯
}
export async function mount() {
// 子應(yīng)用的掛載邏輯
}
export async function unmount() {
// 子應(yīng)用的卸載邏輯
}
在主應(yīng)用中,可以通過 loadMicroApp 方法來動態(tài)加載子應(yīng)用,例如:
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'sub-app',
entry: '//localhost:8080',
container: '#sub-app-container',
});
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)動態(tài)卸載?
qiankun 微前端的子應(yīng)用可以通過導(dǎo)出一個(gè)異步卸載函數(shù)來實(shí)現(xiàn)動態(tài)卸載,例如:
export async function unmount() {
// 子應(yīng)用的卸載邏輯
}
在主應(yīng)用中,可以通過 unloadMicroApp 方法來動態(tài)卸載子應(yīng)用,例如:
import { unloadMicroApp } from 'qiankun';
unloadMicroApp('sub-app');
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)與主應(yīng)用的通信?
qiankun 微前端的子應(yīng)用可以通過 window.POWERED_BY_QIANKUN 全局變量來判斷當(dāng)前應(yīng)用是否運(yùn)行在 qiankun 微前端環(huán)境中,例如:
if (window.__POWERED_BY_QIANKUN__) {
// 子應(yīng)用運(yùn)行在 qiankun 微前端環(huán)境中
}
在子應(yīng)用中,可以通過 window.parent 訪問主應(yīng)用的全局對象,例如:
window.parent.postMessage({ type: 'message', data: 'hello' }, '\*');
在主應(yīng)用中,可以通過 onGlobalStateChange 方法來監(jiān)聽子應(yīng)用的狀態(tài)變化,例如:
import { onGlobalStateChange } from 'qiankun';
onGlobalStateChange((state, prev) => {
// 子應(yīng)用狀態(tài)變化的回調(diào)函數(shù)
});
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)與其他子應(yīng)用的通信?
qiankun 微前端的子應(yīng)用可以通過 window.POWERED_BY_QIANKUN 全局變量來判斷當(dāng)前應(yīng)用是否運(yùn)行在 qiankun 微前端環(huán)境中,例如:
if (window.__POWERED_BY_QIANKUN__) {
// 子應(yīng)用運(yùn)行在 qiankun 微前端環(huán)境中
}
在子應(yīng)用中,可以通過 window.dispatchEvent 方法來觸發(fā)自定義事件,例如:
window.dispatchEvent(new CustomEvent('message', { detail: 'hello' }));
在其他子應(yīng)用中,可以通過 window.addEventListener 方法來監(jiān)聽自定義事件,例如:
window.addEventListener('message', (event) => {
console.log(event.detail); // 'hello'
});
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)路由跳轉(zhuǎn)?
qiankun 微前端的子應(yīng)用可以通過 history.pushState 方法來實(shí)現(xiàn)路由跳轉(zhuǎn),例如:
history.pushState(null, null, '/path');
在主應(yīng)用中,可以通過 setMatchedPath 方法來設(shè)置當(dāng)前子應(yīng)用的路由路徑,例如:
import { setMatchedPath } from 'qiankun';
setMatchedPath('/path');
在主應(yīng)用中,可以通過 onGlobalStateChange 方法來監(jiān)聽子應(yīng)用的路由變化,例如:
import { onGlobalStateChange } from 'qiankun';
onGlobalStateChange((state, prev) => {
console.log(state.matchedPath); // '/path'
});
qiankun 微前端的子應(yīng)用如何實(shí)現(xiàn)樣式隔離?
- qiankun實(shí)現(xiàn)了各個(gè)子應(yīng)用之間的樣式隔離,但是主應(yīng)用和子應(yīng)用之間的樣式隔離沒有實(shí)現(xiàn),所以主應(yīng)用和子應(yīng)用之前的樣式還會有沖突和覆蓋的情況。
- 解決方法:
- 每個(gè)應(yīng)用的樣式使用固定的格式
- 通過 css-module的方式給每個(gè)應(yīng)用自動加上前綴
qiankun主要通過使用Shadow DOM來實(shí)現(xiàn)CSS隔離。
Shadow DOM:Shadow DOM是一種瀏覽器內(nèi)置的Web標(biāo)準(zhǔn)技術(shù),它可以創(chuàng)建一個(gè)封閉的DOM結(jié)構(gòu),這個(gè)DOM結(jié)構(gòu)對外部是隔離的,包括其CSS樣式。qiankun在掛載子應(yīng)用時(shí),會將子應(yīng)用的HTML元素掛載到Shadow DOM上,從而實(shí)現(xiàn)CSS的隔離。
// qiankun使用Shadow DOM掛載子應(yīng)用
const container = document.getElementById('container');
const shadowRoot = container.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<div id="subapp-container"></div>';
潛在的缺點(diǎn)是它需要瀏覽器支持Shadow DOM,這在一些舊的瀏覽器或者不兼容Shadow DOM的瀏覽器中可能會出現(xiàn)問題。
qiankun 微前端如何實(shí)現(xiàn)JS隔離?
【qiankun主要方式】代理沙箱:proxySandbox多例沙箱
原理:激活沙箱后,每次對window取值的時(shí)候,先從自己沙箱環(huán)境的fakeWindow里面找,如果不存在,就從rawWindow(外部的window)里去找;當(dāng)對沙箱內(nèi)部的window對象賦值的時(shí)候,會直接操作fakeWindow,而不會影響到rawWindow。
- 把當(dāng)前 window 的一些原生屬性(如document, location等)拷貝出來,單獨(dú)放在一個(gè)對象上,這個(gè)對象也稱為 fakeWindow
- 之后對每個(gè)微應(yīng)用分配一個(gè) fakeWindow
- 當(dāng)微應(yīng)用修改全局變量時(shí):
- 如果是原生屬性,則修改全局的 window
- 如果不是原生屬性,則修改 fakeWindow 里的內(nèi)容
- 微應(yīng)用獲取全局變量時(shí):
- 如果是原生屬性,則從 window 里拿
-
如果不是原生屬性,則優(yōu)先從 fakeWindow 里獲取
這樣一來連恢復(fù)環(huán)境都不需要了,因?yàn)槊總€(gè)微應(yīng)用都有自己一個(gè)環(huán)境,當(dāng)在 active 時(shí)就給這個(gè)微應(yīng)用分配一個(gè) fakeWindow,當(dāng) inactive 時(shí)就把這個(gè) fakeWindow 存起來,以便之后再利用。
優(yōu)缺點(diǎn)分析:不會污染全局window,支持多個(gè)子應(yīng)用同時(shí)加載。
image.png
qiankun主應(yīng)用和子應(yīng)用之間的全局狀態(tài)管理
- 一般來說說,子應(yīng)用是通過業(yè)務(wù)來劃分的,不同業(yè)務(wù)線應(yīng)該降低耦合度,盡量去避免通信,但是如果涉及到一些公共的狀態(tài)或者操作,qiankun也是支持的。
- qinkun提供了一個(gè)全局的Globalstate來共享數(shù)據(jù),基座初始化之后,子應(yīng)用可以監(jiān)聽到這個(gè)數(shù)據(jù)的變化,也能提交這個(gè)數(shù)據(jù)。
主應(yīng)用:
//主應(yīng)用初始化
import { initGlobalState } from 'qiankun';
const actions = initGlobalState (state);
//主應(yīng)用項(xiàng)目監(jiān)聽和修改
actions.onGlobalStateChange( (state, prev) => {
// state:變更后的狀態(tài);prev 變更前的狀態(tài)
console.log(state,prev);
})
actions.setGlobalState(state);
子應(yīng)用:
//子項(xiàng)目監(jiān)聽和修改
export function mount(props) {
props.onGlobalStateChange( (state, prev) => {
// state:變更后的狀態(tài);prev 變更前的狀態(tài)
console.log(state,prev);
})
props.setGlobalState(state)
}
引用:https://blog.csdn.net/2401_84413092/article/details/138612943
