qiankun 子應(yīng)用keep-alive實(shí)現(xiàn)方案

一、核心設(shè)計(jì)理念

  1. 簡(jiǎn)單

由于主應(yīng)用微應(yīng)用都能做到技術(shù)棧無(wú)關(guān),qiankun 對(duì)于用戶而言只是一個(gè)類似 jQuery 的庫(kù),你需要調(diào)用幾個(gè) qiankun 的 API 即可完成應(yīng)用的微前端改造。同時(shí)由于 qiankun 的 HTML entry 及沙箱的設(shè)計(jì),使得微應(yīng)用的接入像使用 iframe 一樣簡(jiǎn)單。

  1. 解耦/技術(shù)棧無(wú)關(guān)

微前端的核心目標(biāo)是將巨石應(yīng)用拆解成若干可以自治的松耦合微應(yīng)用,而 qiankun 的諸多設(shè)計(jì)均是秉持這一原則,如 HTML entry、沙箱、應(yīng)用間通信等。這樣才能確保微應(yīng)用真正具備 獨(dú)立開(kāi)發(fā)、獨(dú)立運(yùn)行 的能力。

二、快速上手

  1. 安裝 qiankun
    yarn add qiankun # 或者 npm i qiankun -S

2.在主應(yīng)用中注冊(cè)微應(yīng)用


import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);

start();

當(dāng)微應(yīng)用信息注冊(cè)完之后,一旦瀏覽器的 url 發(fā)生變化,便會(huì)自動(dòng)觸發(fā) qiankun 的匹配邏輯,所有 activeRule 規(guī)則匹配上的微應(yīng)用就會(huì)被插入到指定的 container 中,同時(shí)依次調(diào)用微應(yīng)用暴露出的生命周期鉤子。

如果微應(yīng)用不是直接跟路由關(guān)聯(lián)的時(shí)候,你也可以選擇手動(dòng)加載微應(yīng)用的方式:


import { loadMicroApp } from 'qiankun';

loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer',
});

3.微應(yīng)用配置
微應(yīng)用分為有 webpack 構(gòu)建和無(wú) webpack 構(gòu)建項(xiàng)目,有 webpack 的微應(yīng)用(主要是指 Vue、React、Angular)需要做的事情有:

新增 public-path.js 文件,用于修改運(yùn)行時(shí)的 publicPath。
注意:運(yùn)行時(shí)的 publicPath 和構(gòu)建時(shí)的 publicPath 是不同的,兩者不能等價(jià)替代。

微應(yīng)用建議使用 history 模式的路由,需要設(shè)置路由 base,值和它的 activeRule 是一樣的。
在入口文件最頂部引入 public-path.js,修改并導(dǎo)出三個(gè)生命周期函數(shù)。
修改 webpack 打包,允許開(kāi)發(fā)環(huán)境跨域和 umd 打包。
主要的修改就是以上四個(gè),可能會(huì)根據(jù)項(xiàng)目的不同情況而改變。例如,你的項(xiàng)目是 index.html 和其他的所有文件分開(kāi)部署的,說(shuō)明你們已經(jīng)將構(gòu)建時(shí)的 publicPath 設(shè)置為了完整路徑,則不用修改運(yùn)行時(shí)的 publicPath (第一步操作可?。?。

無(wú) webpack 構(gòu)建的微應(yīng)用直接將 lifecycles 掛載到 window 上即可。

此處以vue為例:

1.在src目錄新增 public-path.js:


if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

2.入口文件 main.js 修改,為了避免根 id #app 與其他的 DOM 沖突,需要限制查找范圍。


let instance = null
function render (props = {}) {
  const { container } = props
  instance = new Vue({
    router,
    store,
    render: (h) => h(App)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

// 獨(dú)立運(yùn)行時(shí)
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

export async function bootstrap () {
  console.log('[vue] vue app bootstraped')
}
export async function mount (props) {
  console.log('[vue] props from main framework', props)
  props.onGlobalStateChange((state, prev) => {
    // state: 變更后的狀態(tài); prev 變更前的狀態(tài)
    console.log('微應(yīng)用獲取主應(yīng)用狀態(tài)', state, prev)
    if (state && state.eicloud) {
      store.dispatch('setMicroAppLogin', state.eicloud)
    } else {
      store.dispatch('clearStorage')
    }
  }, true)
  render(props)
}
export async function unmount () {
  store.dispatch('clearStorage')
  instance.$destroy()
  instance.$el.innerHTML = ''
  instance = null
}

三、qiankun微應(yīng)用keep-alive實(shí)現(xiàn)方案
背景:qiankun提供了兩種加載子應(yīng)用的方式 registerMicroApps和loadMicroApp。官方推薦的是registerMicroApps路由匹配激活子應(yīng)用,當(dāng)瀏覽器 url 發(fā)生變化時(shí),會(huì)自動(dòng)檢查每一個(gè)微應(yīng)用注冊(cè)的 activeRule 規(guī)則,符合規(guī)則的應(yīng)用將會(huì)被自動(dòng)激活。但是在微應(yīng)用和主應(yīng)用之間切換時(shí),每次都會(huì)刷新?tīng)顟B(tài),沒(méi)辦法保持上次操作狀態(tài)。此時(shí)就需要用到loadMicroApp來(lái)手動(dòng)加載微應(yīng)用,這種需要手動(dòng)去調(diào)用加載和卸載方法。

實(shí)現(xiàn)方案

  1. 入口文件改造,主應(yīng)用微應(yīng)用用動(dòng)態(tài)組件加載

<template>
  <div id="app-wrapper">
    <keep-alive>
      <component :is="currentName" />
    </keep-alive>
  </div>
</template>

components: { microA, microB, mainApp }, // 注冊(cè)微應(yīng)用,主應(yīng)用組件

setup() {
    const currentName = ref('mainApp')
    const userStore = useUserStore()
    const route = useRoute()
    checkPath(route.path)

// 監(jiān)聽(tīng)路由,手動(dòng)匹配加載微應(yīng)用
    watch(
      () => route.path,
      val => {
        checkPath(val)
      }
    )
    function checkPath(path) {
      const apps = userStore.getMicroApps
      const app = apps.find(item => path.startsWith(`/${item.name}`))
      currentName.value = app ? app.name : 'mainApp'
      if (app) {
        nextTick(() => {
          userStore.registerMicroApps(app) // 手動(dòng)加載微應(yīng)用
        })
      }
    }


    return {
      currentName
    }
  }

  1. user.ts 預(yù)加載微應(yīng)用靜態(tài)資源、手動(dòng)調(diào)用加載

microAppsInstance: {}



// 預(yù)加載微應(yīng)用靜態(tài)資源  在登錄之后調(diào)用

export function prefetchMircoApps() {
  const userStore = useUserStore()
 
  const apps = userStore.getMicroApps.map(({ name, entry }) => {
    return {
      name: name,
      entry
    }
  })
  prefetchApps(apps) // 預(yù)加載微應(yīng)用的靜態(tài)資源
}


export function registerApps(app) {
  const userStore = useUserStore()
  const microAppsInstance = userStore.getMicroAppsInstance
  if (!microAppsInstance[app.name]) {
    console.log('手動(dòng)加載微應(yīng)用:', app.name)
    microAppsInstance[app.name] = loadMicroApp(app)
    userStore.setMicroAppsInstance(microAppsInstance)
  }
}

// 卸載微應(yīng)用
export function uninstallMicroApp() {
  const appsInstance = userStore.getMicroAppsInstance
  for (const key in appsInstance) {
    appsInstance[key].unmount()
  }
  userStore.setMicroAppsInstance({})
}

  1. 卸載微應(yīng)用

到現(xiàn)在加載已經(jīng)完成了,打開(kāi)主應(yīng)用和微應(yīng)用頁(yè)面,來(lái)回切換已經(jīng)實(shí)現(xiàn)了狀態(tài)保持,接下來(lái)就是卸載微應(yīng)用。標(biāo)簽被關(guān)閉的時(shí)候去判斷是否需要卸載。


// 編輯標(biāo)簽

function handleEdit(targetKey: string, action) {
      if (action === 'remove') unmountMicroApp(targetKey)
      // Added operation to hide, currently only use delete operation
      if (unref(unClose)) {
        return
      }



      tabStore.closeTabByKey(targetKey, router)
    }

function unmountMicroApp(targetKey) {
      const appsInstance = userStore.getMicroAppsInstance
      const appKey = targetKey.split('/')[1]
      if (!appsInstance[appKey]) return

// 判斷打開(kāi)的標(biāo)簽中,是否還有該微應(yīng)用的頁(yè)面,如果沒(méi)有則卸載該子應(yīng)用
      if (
        !tabStore.getTabList.some(
          tab => tab.path !== targetKey && tab.path.startsWith(`/${appKey}`)
        )
      ) {
        appsInstance[appKey].unmount()
        delete appsInstance[appKey]
        userStore.setMicroAppsInstance(appsInstance)
      }
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容