基于微前端qiankun的多頁(yè)簽緩存方案實(shí)踐
本文梳理了基于阿里開(kāi)源微前端框架qiankun,實(shí)現(xiàn)多頁(yè)簽及子應(yīng)用緩存的方案,同時(shí)還類(lèi)比了多個(gè)不同方案之間的區(qū)別及優(yōu)劣勢(shì),為使用微前端進(jìn)行多頁(yè)簽開(kāi)發(fā)的同學(xué),提供一些參考。
本文梳理了基于阿里開(kāi)源微前端框架qiankun,實(shí)現(xiàn)多頁(yè)簽及子應(yīng)用緩存的方案,同時(shí)還類(lèi)比了多個(gè)不同方案之間的區(qū)別及優(yōu)劣勢(shì),為使用微前端進(jìn)行多頁(yè)簽開(kāi)發(fā)的同學(xué),提供一些參考。
一、多頁(yè)簽是什么?
我們常見(jiàn)的瀏覽器多頁(yè)簽、編輯器多頁(yè)簽,從產(chǎn)品角度來(lái)說(shuō),就是為了能夠?qū)崿F(xiàn)用戶(hù)訪(fǎng)問(wèn)可記錄,快速定位工作區(qū)等作用;那對(duì)于單頁(yè)應(yīng)用,可以通過(guò)實(shí)現(xiàn)多頁(yè)簽,對(duì)用戶(hù)的訪(fǎng)問(wèn)記錄進(jìn)行緩存,從而提供更好的用戶(hù)體驗(yàn)。
前端可以通過(guò)多種方式實(shí)現(xiàn)多頁(yè)簽,常見(jiàn)的方案有兩種:
- 通過(guò)CSS樣式display:none來(lái)控制頁(yè)面的顯示隱藏模塊的內(nèi)容;
- 將模塊序列化緩存,通過(guò)緩存的內(nèi)容進(jìn)行渲染(與vue的keep-alive原理類(lèi)似,在單頁(yè)面應(yīng)用中應(yīng)用廣泛)。
相對(duì)于第一種方式,第二種方式將DOM格式存儲(chǔ)在序列化的JS對(duì)象當(dāng)中,只渲染需要展示的DOM元素,減少了DOM節(jié)點(diǎn)數(shù),提升了渲染的性能,是當(dāng)前主流的實(shí)現(xiàn)多頁(yè)簽的方式。
那么相對(duì)于傳統(tǒng)的單頁(yè)面應(yīng)用,通過(guò)微前端qiankun進(jìn)行改造后的前端應(yīng)用,在多頁(yè)簽上實(shí)現(xiàn)會(huì)有什么不同呢?
1.1 單頁(yè)面應(yīng)用實(shí)現(xiàn)多頁(yè)簽
改造前的單頁(yè)面應(yīng)用技術(shù)棧是Vue全家桶(vue2.6.10 + element2.15.1 + webpack4.0.0+vue-cli4.2.0)。
vue框架提供了keep-alive來(lái)支持緩存相關(guān)的需求,使用keep-alive即可實(shí)現(xiàn)多頁(yè)簽的基本功能,但是為了支持更多的功能,我們?cè)谄浠A(chǔ)上重新封裝了vue-keep-alive組件。
相對(duì)較于keep-alive通過(guò)include、exclude對(duì)緩存進(jìn)行控制,vue-keep-alive使用更原生的發(fā)布訂閱方式來(lái)刪除緩存,可以實(shí)現(xiàn)更完整的多頁(yè)簽功能,例如同個(gè)路由可以根據(jù)參數(shù)的不同派生出多個(gè)路由實(shí)例(如打開(kāi)多個(gè)詳情頁(yè)頁(yè)簽)以及動(dòng)態(tài)刪除緩存實(shí)例等功能。
下面是vue-keep-alive自定義的拓展實(shí)現(xiàn):
created() { // 動(dòng)態(tài)刪除緩存實(shí)例監(jiān)聽(tīng) this.cache = Object.create(null); breadCompBus.$on('removeTabByKey', this.removeCacheByKey); breadCompBus.$on('removeTabByKeys', (data) => { data.forEach((item) => { this.removeCacheByKey(item); }); }); }
vue-keep-alive組件即可傳入自定義方法,用于自定義vnode.key,支持同一匹配路由中派生多個(gè)實(shí)例。
// 傳入`vue-keep-alive`的自定義方法 function updateComponentsKey(key, name, vnode) { const match = this.$route.matched[1]; if (match && match.meta.multiNodeKey) { vnode.key = match.meta.multiNodeKey(key, this.$route); return vnode.key; } return key; }
1.2 使用qiankun進(jìn)行微前端改造后,多頁(yè)簽緩存有什么不同
qiankun是由螞蟻金服推出的基于Single-Spa實(shí)現(xiàn)的前端微服務(wù)框架,本質(zhì)上還是路由分發(fā)式的服務(wù)框架,不同于原本 Single-Spa采用JS Entry用的方案,qiankun采用HTML Entry 方式進(jìn)行了替代優(yōu)化。
使用qiankun進(jìn)行微前端改造后,頁(yè)面被拆分為一個(gè)基座應(yīng)用和多個(gè)子應(yīng)用,每個(gè)子應(yīng)用都運(yùn)行在獨(dú)立的沙箱環(huán)境中。
相對(duì)于單頁(yè)面應(yīng)用中通過(guò)keep-alive管控組件實(shí)例的方式,拆分后的各個(gè)子應(yīng)用的keep-alive并不能管控到其他子應(yīng)用的實(shí)例,我們需要緩存對(duì)所有的應(yīng)用生效,那么只能將緩存放到基座應(yīng)用中。
這個(gè)就存在幾個(gè)問(wèn)題:
- 加載:主應(yīng)用需要在什么時(shí)候,用什么方式來(lái)加載子應(yīng)用實(shí)例?
- 渲染:通過(guò)緩存實(shí)例來(lái)渲染子應(yīng)用時(shí),是通過(guò)DOM顯隱方式渲染子應(yīng)用還是有其他方式?
- 通信:關(guān)閉頁(yè)簽時(shí),如何判斷是否完全卸載子應(yīng)用,主應(yīng)用應(yīng)該使用什么通信方式告訴子應(yīng)用?
二、方案選擇
通過(guò)在Github issues及掘金等平臺(tái)的一系列資料查找和對(duì)比后,關(guān)于如何在qiankun框架下實(shí)現(xiàn)多頁(yè)簽,在不修改qiankun源碼的前提下,主要有兩種實(shí)現(xiàn)的思路。
2.1 方案一:多個(gè)子應(yīng)用同時(shí)存在
實(shí)現(xiàn)思路:
在dom上通過(guò)v-show控制顯示哪一個(gè)子應(yīng)用,及display:none;控制不同子應(yīng)用dom的顯示隱藏。
url變化時(shí),通過(guò)loadMicroApp手動(dòng)控制加載哪個(gè)子應(yīng)用,在頁(yè)簽關(guān)閉時(shí),手動(dòng)調(diào)用unmount方法卸載子應(yīng)用。
示例:
<template> <div id="app"> <header> <router-link to="/app-vue-hash/">app-vue-hash</router-link> <router-link to="/app-vue-history/">app-vue-history</router-link> <router-link to="/about">about</router-link> </header> <div id="appContainer1" v-show="$route.path.startsWith('/app-vue-hash/')"></div> <div id="appContainer2" v-show="$route.path.startsWith('/app-vue-history/')"></div> <router-view></router-view> </div> </template> <script> import { loadMicroApp } from 'qiankun'; const apps = [ { name: 'app-vue-hash', entry: 'http://localhost:1111', container: '#appContainer1', props: { data : { store, router } } }, { name: 'app-vue-history', entry: 'http://localhost:2222', container: '#appContainer2', props: { data : store } } ] export default { mounted() { // 優(yōu)先加載當(dāng)前的子項(xiàng)目 const path = this.$route.path; const currentAppIndex = apps.findIndex(item => path.includes(item.name)); if(currentAppIndex !== -1){ const currApp = apps.splice(currentAppIndex, 1)[0]; apps.unshift(currApp); } // loadMicroApp 返回值是 app 的生命周期函數(shù)數(shù)組 const loadApps = apps.map(item => loadMicroApp(item)) // 當(dāng) tab 頁(yè)關(guān)閉時(shí),調(diào)用 loadApps 中 app 的 unmount 函數(shù)即可 }, } </script>
具體的DOM展示(通過(guò)display:none;控制不同子應(yīng)用DOM的顯隱):
方案優(yōu)勢(shì):
- loadMicroApp是qiankun提供的API,可以方便快速接入;
- 該方式不卸載子應(yīng)用,頁(yè)簽切換速度比較快。
方案不足:
- 子應(yīng)用切換時(shí)不銷(xiāo)毀DOM,會(huì)導(dǎo)致DOM節(jié)點(diǎn)和事件監(jiān)聽(tīng)過(guò)多,嚴(yán)重時(shí)會(huì)造成頁(yè)面卡頓;
- 子應(yīng)用切換時(shí)未卸載,路由事件監(jiān)聽(tīng)也未卸載,需要對(duì)路由變化的監(jiān)聽(tīng)做特殊的處理。
2.2 方案二:同一時(shí)間僅加載一個(gè)子應(yīng)用,同時(shí)保存其他應(yīng)用的狀態(tài)
實(shí)現(xiàn)思路:
- 通過(guò)registerMicroApps注冊(cè)子應(yīng)用,qiankun會(huì)通過(guò)自動(dòng)加載匹配的子應(yīng)用;
- 參考keep-alive實(shí)現(xiàn)方式,每個(gè)子應(yīng)用都緩存自己實(shí)例的vnode,下次進(jìn)入子應(yīng)用時(shí)可以直接使用緩存的vnode直接渲染為真實(shí)DOM。
方案優(yōu)勢(shì):
- 同一時(shí)間,只是展示一個(gè)子應(yīng)用的active頁(yè)面,可減少DOM節(jié)點(diǎn)數(shù);
- 非active子應(yīng)用卸載時(shí)同時(shí)會(huì)卸載DOM及不需要的事件監(jiān)聽(tīng),可釋放一定內(nèi)存。
方案不足:
- 沒(méi)有現(xiàn)有的API可以快速實(shí)現(xiàn),需要自己管理子應(yīng)用緩存,實(shí)現(xiàn)較為復(fù)雜;
- DOM渲染多了一個(gè)從虛擬DOM轉(zhuǎn)化為真實(shí)DOM的一個(gè)過(guò)程,渲染時(shí)間會(huì)比第一種方案稍多。
vue組件實(shí)例化過(guò)程簡(jiǎn)介
這里簡(jiǎn)單的回顧下vue的幾個(gè)關(guān)鍵的渲染節(jié)點(diǎn):
vue關(guān)鍵渲染節(jié)點(diǎn)(來(lái)源:掘金社區(qū))
- compile:對(duì)template進(jìn)行編譯,將AST轉(zhuǎn)化后生成render function;
- render:生成VNODE虛擬DOM;
- patch :將虛擬DOM轉(zhuǎn)換為真實(shí)DOM;
因此,方案二相對(duì)于方案一,就是多了最后patch的過(guò)程。
2.3 最終選擇
根據(jù)兩種方案優(yōu)勢(shì)與不足的評(píng)估,同時(shí)根據(jù)我們項(xiàng)目的具體情況,最終選擇了方案二進(jìn)行實(shí)現(xiàn),具體原因如下:
- 過(guò)多的DOM及事件監(jiān)聽(tīng),會(huì)造成不必要的內(nèi)存浪費(fèi),同時(shí)我們的項(xiàng)目主要以編輯器展示和數(shù)據(jù)展示為主,單個(gè)頁(yè)簽內(nèi)內(nèi)容較多,會(huì)更傾向于關(guān)注內(nèi)存使用情況;
- 方案二在子應(yīng)用二次渲染時(shí)多了一個(gè)patch過(guò)程,渲染速度不會(huì)慢多少,在可接受范圍內(nèi)。
三、具體實(shí)現(xiàn)
在上面一部分我們簡(jiǎn)單的描述了方案二的一個(gè)實(shí)現(xiàn)思路,其核心思想就是是通過(guò)緩存子應(yīng)用實(shí)例的vnode,那么這一部分,就來(lái)看下它的一個(gè)具體的實(shí)現(xiàn)的過(guò)程。
3.1 從組件級(jí)別的緩存到應(yīng)用級(jí)別的緩存
在vue中,keep-alive組件通過(guò)緩存vnode的方式,實(shí)現(xiàn)了組件級(jí)別的緩存,對(duì)于通過(guò)vue框架實(shí)現(xiàn)的子應(yīng)用來(lái)說(shuō),它其實(shí)也是一個(gè)vue實(shí)例,那么我們同樣也可以做到通過(guò)緩存vnode的方式,實(shí)現(xiàn)應(yīng)用級(jí)別的緩存。
通過(guò)分析keep-alive源碼,我們了解到keep-alive是通過(guò)在render中進(jìn)行緩存命中,返回對(duì)應(yīng)組件的vnode,并在mounted和updated兩個(gè)生命周期鉤子中加入對(duì)子組件vnode的緩存。
// keep-alive核心代碼 render () { const slot = this.$slots.default const vnode: VNode = getFirstComponentChild(slot) const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // 更多代碼... // 緩存命中 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance // make current key freshest remove(keys, key) keys.push(key) } else { // delay setting the cache until update this.vnodeToCache = vnode this.keyToCache = key } // 設(shè)置keep-alive,防止再次觸發(fā)created等生命周期 vnode.data.keepAlive = true } return vnode || (slot && slot[0]) } // mounted和updated時(shí)緩存當(dāng)前組件的vnode mounted() { this.cacheVNode() } updated() { this.cacheVNode() }
相對(duì)于keep-alive需要在mounted和updated兩個(gè)生命周期中對(duì)vnode緩存進(jìn)行更新,在應(yīng)用級(jí)的緩存中,我們只需要在子應(yīng)用卸載時(shí),主動(dòng)對(duì)整個(gè)實(shí)例的vnode進(jìn)行緩存即可。
// 父應(yīng)用提供unmountCache方法 function unmountCache() { // 此處永遠(yuǎn)只會(huì)保存首次加載生成的實(shí)例 const needCached = this.instance?.cachedInstance || this.instance; const cachedInstance = {}; cachedInstance._vnode = needCached._vnode; // keepalive設(shè)置為必須 防止進(jìn)入時(shí)再次created,同keep-alive實(shí)現(xiàn) if (!cachedInstance._vnode.data.keepAlive) cachedInstance._vnode.data.keepAlive = true; // 省略其他代碼... // loadedApplicationMap用于是key-value形式,用于保存當(dāng)前應(yīng)用的實(shí)例 loadedApplicationMap[this.cacheKey] = cachedInstance; // 省略其他代碼... // 卸載實(shí)例 this.instance.$destroy(); // 設(shè)置為null后可進(jìn)行垃圾回收 this.instance = null; } // 子應(yīng)用在qiankun框架提供的卸載方法中,調(diào)用unmountCache export async function unmount() { console.log('[vue] system app unmount'); mainService.unmountCache(); }
3.2 移花接木——將vnode重新掛載到一個(gè)新實(shí)例上
將vnode緩存到內(nèi)存中后,再將原有的instance卸載,重新進(jìn)入子應(yīng)用時(shí),就可以使用緩存的vnode進(jìn)行render渲染。
// 創(chuàng)建子應(yīng)用實(shí)例,有緩存的vnode則使用緩存的vnode function newVueInstance(cachedNode) { const config = { router: this.router, store: this.store, render: cachedNode ? () => cachedNode : instance.render, // 優(yōu)先使用緩存vnode }); return new Vue(config); } // 實(shí)例化子應(yīng)用實(shí)例,根據(jù)是否有緩存vnode確定是否傳入cachedNode this.instance = newVueInstance(cachedNode); this.instance.$mount('#app');
那么,這里不禁就會(huì)有些疑問(wèn):
- 如果我們每次進(jìn)入子應(yīng)用時(shí),都重新創(chuàng)建一個(gè)實(shí)例,那么為什么還要卸載,直接不卸載就可以了嗎?
- 將緩存vnode使用到一個(gè)新的實(shí)例上,不會(huì)有什么問(wèn)題嗎?
首先我們回答一下第一個(gè)問(wèn)題,為什么在切換子應(yīng)用時(shí),要卸載掉原來(lái)的子應(yīng)用實(shí)例,有兩個(gè)考慮方面:
- 其一,是對(duì)內(nèi)存的考量,我們需要的其實(shí)僅僅是vnode,而不是整個(gè)實(shí)例,緩存整個(gè)實(shí)例是方案一的實(shí)現(xiàn)方案,所以,我們僅需要緩存我們需要的對(duì)象即可;
- 其二,卸載子應(yīng)用實(shí)例可以移除不必要的事件監(jiān)聽(tīng),比如vue-router對(duì)popstate事件就進(jìn)行了監(jiān)聽(tīng),我們?cè)谄渌討?yīng)用操作時(shí),并不希望原來(lái)的子應(yīng)用也對(duì)這些事件進(jìn)行響應(yīng),那么在子應(yīng)用卸載時(shí),就可以移除掉這些監(jiān)聽(tīng)。
對(duì)于第二個(gè)問(wèn)題,情況會(huì)更加復(fù)雜一點(diǎn),下面一個(gè)部分,就主要來(lái)看下主要遇到了哪些問(wèn)題,又該如何去解決。
3.3 解決應(yīng)用級(jí)緩存方案的問(wèn)題
3.3.1 vue-router相關(guān)問(wèn)題
- 在實(shí)例卸載后對(duì)路由變化監(jiān)聽(tīng)失效;
- 新的vue-router對(duì)原有的router params等參數(shù)記錄失效。
首先我們需要明確這兩個(gè)問(wèn)題的原因:
- 第一個(gè)是因?yàn)樵谧討?yīng)用卸載時(shí)移除了對(duì)popstate事件的監(jiān)聽(tīng),那么我們需要做的就是重新注冊(cè)對(duì)popstate事件的監(jiān)聽(tīng),這里可以通過(guò)重新實(shí)例化一個(gè)vue-router解決;
- 第二問(wèn)題是因?yàn)橥ㄟ^(guò)重新實(shí)例化vue-router解決第一個(gè)問(wèn)題之后,實(shí)際上是一個(gè)新的vue-router,我們需要做的就是不僅要緩存vnode,還需要緩存router相關(guān)的信息。
大致的解決實(shí)現(xiàn)如下:
// 實(shí)例化子應(yīng)用vue-router function initRouter() { const { router: originRouter } = this.baseConfig; const config = Object.assign(originRouter, { base: `app-kafka/`, }); Vue.use(VueRouter); this.router = new VueRouter(config); } // 創(chuàng)建子應(yīng)用實(shí)例,有緩存的vnode則使用緩存的vnode function newVueInstance(cachedNode) { const config = { router: this.router, // 在vue init過(guò)程中,會(huì)重新調(diào)用vue-router的init方法,重新啟動(dòng)對(duì)popstate事件監(jiān)聽(tīng) store: this.store, render: cachedNode ? () => cachedNode : instance.render, // 優(yōu)先使用緩存vnode }); return new Vue(config); } function render() { if(isCache) { // 場(chǎng)景一、重新進(jìn)入應(yīng)用(有緩存) const cachedInstance = loadedApplicationMap[this.cacheKey]; // router使用緩存命中 this.router = cachedInstance.$router; // 讓當(dāng)前路由在最初的Vue實(shí)例上可用 this.router.apps = cachedInstance.catchRoute.apps; // 使用緩存vnode重新實(shí)例化子應(yīng)用 const cachedNode = cachedInstance._vnode; this.instance = this.newVueInstance(cachedNode); } else { // 場(chǎng)景二、首次加載子應(yīng)用/重新進(jìn)入應(yīng)用(無(wú)緩存) this.initRouter(); // 正常實(shí)例化 this.instance = this.newVueInstance(); } } function unmountCache() { // 省略其他代碼... cachedInstance.$router = this.instance.$router; cachedInstance.$router.app = null; // 省略其他代碼... }
3.3.2 父子組件通信
多頁(yè)簽的方式增加了父子組件通信的頻率,qiankun有提供setGlobalState通信方式,但是在單應(yīng)用模式下,同一時(shí)間僅支持和一個(gè)子應(yīng)用進(jìn)行通行,對(duì)于unmount 的子應(yīng)用來(lái)說(shuō),無(wú)法接收到父應(yīng)用的通信,因此,對(duì)于不同的場(chǎng)景,我們需要更加靈活的通信方式。
子應(yīng)用——父應(yīng)用:使用qiankun自帶通信方式;
從子到父的通信場(chǎng)景較為簡(jiǎn)單,一般只有路由變化時(shí)進(jìn)行上報(bào),并且僅為激活狀態(tài)的子應(yīng)用才會(huì)上報(bào),可直接使用qiankun自帶通信方式;
父應(yīng)用——子應(yīng)用:使用自定義事件通信;
父應(yīng)用到子應(yīng)用,不僅需要和active狀態(tài)的子應(yīng)用通信,還需要和當(dāng)前處于緩存中子應(yīng)用通信;
因此,父應(yīng)用到子應(yīng)用,通過(guò)自定義事件的方式,能夠?qū)崿F(xiàn)父應(yīng)用和多個(gè)子應(yīng)用的通信。
// 自定義事件發(fā)布 const evt = new CustomEvent('microServiceEvent', { detail: { action: { name: action, data }, basePath, // 用于子應(yīng)用唯一標(biāo)識(shí) }, }); document.dispatchEvent(evt); // 自定義事件監(jiān)聽(tīng) document.addEventListener('microServiceEvent', this.listener);
3.3.3 緩存管理,防止內(nèi)存泄露
- 使用緩存最重要的事項(xiàng)就是對(duì)緩存的管理,在不需要的時(shí)候及時(shí)清理,這在JS中是非常重要但很容易被忽略的事項(xiàng)。
應(yīng)用級(jí)緩存
- 子應(yīng)用vnode、router等屬性,子應(yīng)用切換時(shí)緩存;
頁(yè)面級(jí)緩存
- 通過(guò)vue-keep-alive緩存組件的vnode;
- 刪除頁(yè)簽時(shí),監(jiān)聽(tīng)remove事件,刪除頁(yè)面對(duì)應(yīng)的vnode;
- vue-keep-alive組件中所有緩存均被刪除時(shí),通知?jiǎng)h除整個(gè)子應(yīng)用緩存;
3.4 整體框架
最后,我們從整體的視角來(lái)了解下多頁(yè)簽緩存的實(shí)現(xiàn)方案。
因?yàn)椴粌H僅需要對(duì)子應(yīng)用的緩存進(jìn)行管理,還需要將vue-keep-alive組件注冊(cè)到各個(gè)子應(yīng)用中等事項(xiàng),我們將這些服務(wù)統(tǒng)一在主應(yīng)用的mainService中進(jìn)行管理,在registerMicroApps注冊(cè)子應(yīng)用時(shí)通過(guò)props傳入子應(yīng)用,這樣就能夠?qū)崿F(xiàn)同一套代碼,多處復(fù)用。
// 子應(yīng)用main.js let mainService = null; export async function mount(props) { mainService = null; const { MainService } = props; // 注冊(cè)主應(yīng)用服務(wù) mainService = new MainService({ // 傳入對(duì)應(yīng)參數(shù) }); // 實(shí)例化vue并渲染 mainService.render(props); } export async function unmount() { mainService.unmountCache(); }
最后對(duì)關(guān)鍵流程進(jìn)行梳理:
四、現(xiàn)有問(wèn)題
4.1 暫時(shí)只支持vue框架的實(shí)例緩存
該方案也是基于vue現(xiàn)有特性支持實(shí)現(xiàn)的,在react社區(qū)中對(duì)于多頁(yè)簽實(shí)現(xiàn)并沒(méi)有統(tǒng)一的實(shí)現(xiàn)方案,筆者也沒(méi)有過(guò)多的探索,考慮到現(xiàn)有項(xiàng)目是以vue技術(shù)棧為主,后期升級(jí)也會(huì)只升級(jí)到vue3.0,在一段時(shí)間內(nèi)是可以完全支持的。
五、總結(jié)
相較于社區(qū)上大部分通過(guò)方案一進(jìn)行實(shí)現(xiàn),本文提供了另一種實(shí)現(xiàn)多頁(yè)簽緩存的一種思路,主要是對(duì)子應(yīng)用緩存處理上有些許的不同,大致的思路及通信的方式都是互通的。
另外本文對(duì)qiankun框架的使用沒(méi)有做太多的發(fā)散總結(jié),官網(wǎng)和Github上已經(jīng)有很多相關(guān)問(wèn)題的總結(jié)和踩坑經(jīng)驗(yàn)可供參考。
最后,如果文章有什么問(wèn)題或錯(cuò)誤,歡迎指出,謝謝。
參考閱讀
[Feature Request] 主應(yīng)用多頁(yè)簽切換不同子應(yīng)用的頁(yè)面狀態(tài)保持 #361
基于qiankun的微前端多頁(yè)簽項(xiàng)目實(shí)踐與總結(jié)
到此這篇關(guān)于基于微前端qiankun的多頁(yè)簽緩存方案實(shí)踐的文章就介紹到這了,更多相關(guān)微前端qiankun多頁(yè)簽緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用JS簡(jiǎn)單實(shí)現(xiàn)九宮格抽獎(jiǎng)的示例代碼
在網(wǎng)上經(jīng)常看見(jiàn)一些抽獎(jiǎng)頁(yè)面,也玩過(guò)不同類(lèi)型的抽獎(jiǎng)活動(dòng),但是一直沒(méi)有做過(guò)抽獎(jiǎng)的功能,所以今天來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的九宮格抽獎(jiǎng)功能,文中有詳細(xì)的代碼示例供大家參考,感興趣的朋友可以自己動(dòng)手嘗試一下2023-12-12微信小程序商城項(xiàng)目之購(gòu)物數(shù)量加減(3)
這篇文章主要為大家詳細(xì)介紹了微信小程序商城購(gòu)物數(shù)量加減功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04微信小程序常用簡(jiǎn)易小函數(shù)總結(jié)
這篇文章主要介紹了微信小程序常用簡(jiǎn)易小函數(shù),結(jié)合實(shí)例形式總結(jié)分析了微信小程序提示、登陸、驗(yàn)證、session操作等相關(guān)操作函數(shù)與使用技巧,需要的朋友可以參考下2019-02-02javascript判斷移動(dòng)端訪(fǎng)問(wèn)設(shè)備并解析對(duì)應(yīng)CSS的方法
這篇文章主要介紹了javascript判斷移動(dòng)端訪(fǎng)問(wèn)設(shè)備并解析對(duì)應(yīng)CSS的方法,涉及移動(dòng)端設(shè)備的判斷及動(dòng)態(tài)加載技巧,需要的朋友可以參考下2015-02-02原生javascript實(shí)現(xiàn)類(lèi)似vue的數(shù)據(jù)綁定功能示例【觀察者模式】
這篇文章主要介紹了原生javascript實(shí)現(xiàn)類(lèi)似vue的數(shù)據(jù)綁定功能,結(jié)合實(shí)例形式分析了JavaScript基于觀察者模式實(shí)現(xiàn)類(lèi)似vue的數(shù)據(jù)綁定相關(guān)操作技巧,需要的朋友可以參考下2020-02-02javascript中使用new與不使用實(shí)例化對(duì)象的區(qū)別
這篇文章主要介紹了javascript中使用new與不使用實(shí)例化對(duì)象的區(qū)別的相關(guān)資料,需要的朋友可以參考下2015-06-06原生js實(shí)現(xiàn)隨機(jī)點(diǎn)名
這篇文章主要為大家詳細(xì)介紹了原生js實(shí)現(xiàn)隨機(jī)點(diǎn)名,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07