一、微前端之实践应用间的全局状态管理、应用缓存和预加载子应用
全局状态管理,应用间的全局 store
,如下所示:
在 micro
下的 store
中 index.js
,对外暴露 createStore
,使用 store
管理 initData
,observers
管理所有的订阅者和依赖,通过 getStore
获取 store
,通过 update
更新 store
。当 store
发生变化,通知订阅者,执行 store
的操作,进行缓存,将 store
更新为 value
,通知所有的订阅者,监听 store
的变化。subscribe
是添加订阅者,index.js
,代码如下:
export const createStore = ( initData = { } ) => ( ( ) => {
let store = initData;
const observers = [ ] ;
const getStore = ( ) => store;
const update = ( value ) => {
if ( value !== store) {
const oldValue = store;
store = value;
observers. forEach ( async item => await item ( store, oldValue) ) ;
}
}
const subscribe = ( fn ) => {
observers. push ( fn) ;
}
return {
getStore,
update,
subscribe,
}
} ) ( )
在主应用 main
的 util
中 index.js
,通过 createStore
创建全局状态管理,以 getStore
获取到存的所有 store
数据。通过 window.store
将 store
挂载到 window
上,通过 store.subscribe
添加订阅者。通过 store.update
更新 store
数据,将之前的 store
数据和所要修改的数据进行合并修改,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 ( )
}
在 vue3
子应用中的 main.js
,在 mount
生命周期中,通过 window.store.getStore
获取到 store
里面的数据,通过 window.store.update
修改 store
里面的数据,main.js
,代码如下:
import { createApp } from 'vue' ;
import App from './App.vue' ;
import router from './router' ;
import { setMain } from './utils/global'
let instance = null ;
function render ( ) {
instance = createApp ( App) ;
instance
. use ( router)
. mount ( '#app' ) ;
}
if ( ! window. __MICRO_WEB__) {
render ( ) ;
}
export async function bootstrap ( ) {
console. log ( 'vue3.0 app bootstrap' ) ;
}
export async function mount ( app ) {
setMain ( app) ;
const storeData = window. store. getStore ( ) ;
window. store. update ( { ... storeData, a : 11 } ) ;
render ( ) ;
}
export async function unmount ( ctx ) {
instance. unmount ( ) ;
instance = null ;
const { container } = ctx;
if ( container) {
document. querySelector ( container) . innerHTML = '' ;
}
}
提高加载性能,应用缓存,如下所示:
在 micro
下的 loader
中 index.js
,在 parseHtml
解析 html
时,使用 cache
,根据子应用的 name
来做缓存。通过 cache[name]
判断是否命中 name
,使用缓存,返回对应的内容。同时,将 dom、allScript
直接缓存,后面可以直接使用 cache
,获取到对应的子应用,代码如下:
const cache = { } ;
export const parseHtml = async ( entry, name ) => {
if ( cache[ name] ) {
return cache[ name] ;
}
const html = await fetchResource ( entry) ;
let allScript = [ ] ;
const div = document. createElement ( 'div' ) ;
div. innerHTML = html;
const [ dom, scriptUrl, script] = await getResources ( div, entry) ;
const fetchedScripts = await Promise. all ( scriptUrl. map ( async item => fetchResource ( item) ) ) ;
allScript = script. concat ( fetchedScripts) ;
cache[ name] = [ dom, allScript] ;
return [ dom, allScript] ;
}
提高加载性能,预加载子应用,如下所示:
在 micro
下的 loader
中 prefetch.js
,在 prefetch
中,先获取到所有子应用列表,不包括当前正在显示的。然后,通过 Promise.all
预加载剩下的所有子应用,prefetch.js
,代码如下:
import { getList } from '../const/subApps' ;
import { parseHtml } from './index' ;
export const prefetch = async ( ) => {
const list = getList ( ) . filter ( item => ! window. location. pathname. startsWith ( item. activeRule) ) ;
await Promise. all ( list. map ( async item => await parseHtml ( item. entry, item. name) ) ) ;
}
在 micro
下的 start.js
中,通过 currentApp
渲染要加载的子应用,然后通过 prefetch
预加载剩下的所有子应用,但是不显示,代码如下:
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) ;
}
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 ( ) ;
}