首个子应用"/>
微前端之实践子应用的注册、路由拦截和获取首个子应用
一、微前端之实践子应用的注册、路由拦截和获取首个子应用
- 子应用的注册,如下所示:
- 首先需要一个子应用注册的列表,在主应用
main
的store
文件夹中,新建sub.js
,对外暴露subNavList
这个子应用的列表。subNavList
是一个数组,每一个子元素都是一个子应用,name
是子应用名称,唯一ID
;entry
是子应用的入口,获取到子应用的文件;container
是子应用的渲染内容,显示在哪个容器中;activeRule
是子应用激活路由,根路由内容,激活的规则,loading
是子应用的加载状态;loading
是每一个子应用的信息,sub.js
,代码如下:
import { loading } from '../store'
import * as appInfo from '../store'export const subNavList = [{name: 'react15',entry: '//localhost:9002/', loading,container: '#micro-container', activeRule: '/react15', appInfo,},{name: 'react16',entry: '//localhost:9003/',loading,container: '#micro-container',activeRule: '/react16',appInfo,},{name: 'vue2',entry: '//localhost:9004/',loading,container: '#micro-container',activeRule: '/vue2',appInfo,},{name: 'vue3',entry: '//localhost:9005/',loading,container: '#micro-container',activeRule: '/vue3',appInfo,},
];
- 在主应用
main
的store
文件夹中,有header.js、loading.js、nav.js、sub.js
和index.js,loading.js
,代码如下:
import { ref } from 'vue';
export let loadingStatus = ref(true);
export const changeLoading = type => loadingStatus.value = type;
header.js
,代码如下:
import { ref } from 'vue';
export const headerStatus = ref(true);
export const changeHeader = type => headerStatus.value = type;
nav.js
,代码如下:
import { ref } from 'vue';
export const navStatus = ref(true);
export const changeNav = type => navStatus.value = type;
index.js
,代码如下:
export * as loading from './loading';
export * as header from './header';
export * as nav from './nav';
- 在主应用
main
中,main.js
,通过registerApp
将subNavList
注册到util
下的index.js
,微前端框架中,main.js
,代码如下:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { subNavList } from './store/sub'
import { registerApp } from './util'registerApp(subNavList);
createApp(App).use(router()).mount('#micro_web_main_app');
- 在主应用
main
中,util
下的index.js
,对外暴露registerApp
,通过将传入的子应用注册列表list
注册到微前端框架里,registerMicroApps
,同时也传入了主应用的生命周期,beforeLoad
开始加载,mounted
渲染完成,destoryed
卸载完成。通过调用start
开启微前端框架,util
下的index.js
,代码如下:
import { registerMicroApps, start, createStore } from '../../micro'import { loading } from '../store'const store = createStore()
// const storeData = store.getStore()window.store = store
store.subscribe((newValue, oldValue) => {console.log(newValue, oldValue, '---')
})
// store.update({ ...storeData, a: 1 })export const registerApp = (list) => {registerMicroApps(list, {beforeLoad: [() => {loading.changeLoading(true)console.log('开始加载')}],mounted: [() => {loading.changeLoading(false)console.log('渲染完成')}],destoryed: [() => {console.log('卸载完成')}]})start()
}
- 在
micro
微前端框架中,之前调用的,registerMicroApps
在micro
下的start.js
中,它需要使用到micro
下的const
的subApps.js
,其中list
是统一管理子应用列表,getList
是获取子应用列表,setList
是将子应用列表注册到list
中,subApps.js
,代码如下:
let list = []
export const getList = () => list
export const setList = appList => list = appList
- 在
micro
下的start.js
中,对外暴露registerMicroApps
这个方法,传入子应用注册列表appList
和主应用的生命周期lifeCycle
,分别调用setList
和setMainLifecycle
进行注册设置,代码如下:
export const registerMicroApps = (appList, lifeCycle) => {setList(appList);setMainLifecycle(lifeCycle);// window.appList = appList;
}
- 路由拦截,如下所示:
- 首先需要在
micro
微前端框架中的start.js
,调用rewriteRouter
方法实现路由拦截。在router
下的rewriteRouter.js
中,对外暴露出rewriteRouter
,重写window
的路由跳转。patchRouter
接收两个参数,原生的事件、事件的名称,所有触发pushState
和replaceState
的事件都会被监听到,window.history.pushState
和window.history.replaceState
。通过window.addEventListener
事件绑定micro_push
和micro_replace
,通过window.onpopstate
事件监听,监听返回事件,当点了返回按钮后turnApp
就会执行,rewriteRouter.js
,代码如下:
import { patchRouter } from '../utils'
import { turnApp } from './routerHandle'export const rewriteRouter = () => {window.history.pushState = patchRouter(window.history.pushState, 'micro_push');window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace');window.addEventListener('micro_push', turnApp);window.addEventListener('micro_replace', turnApp);window.onpopstate = async function () {await turnApp()}
}
patchRouter
是在micro
下的util
中的index.js
,给当前的路由跳转打补丁。通过new Event
创建新的事件,通过globalEvent.apply
传递过来的原生事件globalEvent
,代替函数的执行,通过window.dispatchEvent
触发创建的事件,代码如下:
export const patchRouter = (globalEvent, eventName) => {return function () {const e = new Event(eventName)globalEvent.apply(this, arguments) window.dispatchEvent(e) }
}
turnApp
是在micro
下的router
中的routerHandle.js
,如果子应用发生改变在里面可以执行微前端的生命周期执行,代码如下:
import { isTurnChild } from '../utils'
import { lifecycle } from '../lifeCycle'
export const turnApp = async () => {if (isTurnChild()) {await lifecycle()}
}
- 获取首个子应用,如下所示:
- 在
registerApp
中调用micro
下的start.js
,对外暴露的start
方法启动微前端框架。通过getList
首先验证当前子应用列表是否为空,如果子应用列表为空,抛出异常。如果子应用列表非空,有子应用的内容, 查找到符合当前路由的子应用。如果当前没有在使用的子应用, 抛出一个错误,请访问正确的连接,访问一个默认的路由,通常为首页或登录页面,window.history.pushState
跳转到对应的url
上。若查找到子应用的内容并且hash
值存在,进行标记,将当前app
的内容设置为app.activeRule
,start.js
,代码如下:
import { setList, getList } from './const/subApps'
import { currentApp } from './utils'
import { rewriteRouter } from './router/rewriteRouter'
import { setMainLifecycle } from './const/mainLifeCycle'
import { prefetch } from './loader/prefetch'
import { Custom } from './customevent'const custom = new Custom()
custom.on('test', (data) => {console.log(data)
})window.custom = custom;rewriteRouter();export const registerMicroApps = (appList, lifeCycle) => {setList(appList);setMainLifecycle(lifeCycle);// window.appList = appList;
}export const start = () => {const apps = getList();if (!apps.length) {throw Error('子应用列表为空, 请正确注册');}const app = currentApp();const { pathname, hash } = window.location;if (!hash) {window.history.pushState(null, null, '/vue3#/index'); }if (app && hash) {const url = pathname + hash;window.__CURRENT_SUB_APP__ = app.activeRule;window.history.pushState('', '', url);}prefetch();
}
- 调用
currentApp
时,是在micro
的utils
下的index.js
,通过window.location.pathname
获取到当前子应用的路由activeRule
,调用filterApp
子应用在命中后就会返回,isTurnChild
是判断子应用是否做了切换,index.js
,代码如下:
import { getList } from "../const/subApps";
export const patchRouter = (globalEvent, eventName) => {return function () {const e = new Event(eventName); globalEvent.apply(this, arguments); window.dispatchEvent(e); }
}export const currentApp = () => {const currentUrl = window.location.pathname;return filterApp('activeRule', currentUrl);
}export const findAppByRoute = (router) => {return filterApp('activeRule', router);
}export const filterApp = (key, value) => {const currentApp = getList().filter(item => item[key] === value);return currentApp && currentApp.length ? currentApp[0] : {};
}export const isTurnChild = () => {const { pathname, hash } = window.location;const url = pathname + hash;const currentPrefix = url.match(/(\/\w+)/g);if (currentPrefix &&(currentPrefix[0] === window.__CURRENT_SUB_APP__) &&hash === window.__CURRENT_HASH__) {return false;}window.__ORIGIN_APP__ = window.__CURRENT_SUB_APP__;const currentSubApp = window.location.pathname.match(/(\/\w+)/);if (!currentSubApp) {return false}window.__CURRENT_SUB_APP__ = currentSubApp[0];window.__CURRENT_HASH__ = hash;return true;
}
更多推荐
微前端之实践子应用的注册、路由拦截和获取首个子应用
发布评论