一、微前端之实践应用间的全局状态管理、应用缓存和预加载子应用
- 全局状态管理,应用间的全局
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();
}