目前有关微前端通信的方案无非两种:
使用qiankun提供的api-initGlobalState进行通信;
通过在主、微应用中定义状态管理类的方式进行通信;
下面我们就第一种方案进行详细讲解,毕竟官方提供了实现方案。达到的效果如下:
主应用作为中转,进行状态承接与派发;
各个微应用不仅能获取主应用的状态变更,还能同步自身状态到主应用;
微应用能独立运行;
我们先来看看initGlobalState(state) 这个api的具体信息:
1.用法:在主应用中使用,定义全局状态,微应用通过props传参获取;
2.返回:
onGlobalStateChange(callback, Immediately)在当前应用监听全局状态变化;
setGlobalState(state)按照一级属性进行状态设置,微应用只能修改一级属性;
offGlobalStateChange()移除当前的状态监听,微应用在unmount时默认调用;
下面我们在主应用中进行初始化:
import { registerMicroApps, start, setDefaultMountApp, initGlobalState } from 'qiankun'
const { onGlobalStateChange } = initGlobalState({iptValue: ''})
onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log('main-app onGlobalStateChange', state, prev)
})
这里我们只简单定义了一个一级属性iptValue,用于在微应用中绑定表单。同时监听状态变化,打印相关信息。
主应用中状态初始化成功后,整个state状态由qiankun框架内部进行维护,开发者通过官方提供的api进行state的修改、追踪。
因此在每一个微应用中,我们需要做的事情有两个,即:
1.同步主应用状态变化到微应用;
2.同步微应用状态变化到主应用;
下面分别讲解在vue、react中如何进行状态的双向同步。
3.1 vue微应用
我们需要在之前vue入口文件main.js中进行扩展,修改render(props)函数。微应用内部状态管理我们使用vuex,通过vuex提供的api进行状态双向同步。
第一步新建store/index.js文件:
const state = {
store: {}
}
const getters = {
iptValue: state => state.store.iptValue
}
const mutations = {
initStore(state, data) {
state.store = data
},
setStore(state, data) {
state.store = {
...state.store,
...data
}
}
}
const actions = {}
export default {
actions,
getters,
state,
mutations,
modules: {},
strict: false,
plugins: [],
}
这里我们只是定义了store的结构,但是并没有生成vuex的实例对象。
第二步初始化微应用store,绑定状态同步逻辑:
import Store from './store/index'
/**
* 初始化主微应用通信逻辑
* 1.主应用状态变更同步到微应用;
* 2.微应用状态变更同步到主应用;
*/
function initStore(props) {
const myPlugin = store => {
let prevState = _.cloneDeep(store.state)
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
let nextState = _.cloneDeep(state)
if(JSON.stringify(nextState) !== JSON.stringify(prevState)) {
prevState = nextState
// 微应用中store变更后,将状态更新到主应用
props.setGlobalState &&
props.setGlobalState({...state.store})
}
})
}
const storeInstance = new Vuex.Store({
...Store,
plugins: [myPlugin]
})
// 主应用状态变化后,同步到微应用
props.onGlobalStateChange &&
props.onGlobalStateChange(
(state, prev) => {
storeInstance.commit('initStore', state)
console.log('vue-app onGlobalStateChange', state, prev)
}, true
)
return storeInstance
}
主应用状态变更同步到微应用比较简单,在onGlobalStateChange方法中调用commit进行同步即可。
微应用状态变更同步到主应用我们使用setGlobalState()方法,关键是要实时监听微应用状态变更。这里我们使用vuex插件提供的派发功能subscribe,同步之前会比较两次状态是否存在变更,否则会陷入死循环。
第三步为vue实例绑定store:
function render(props = {}) {
...
store = initStore(props)
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app')
}
这样vue微应用状态同步逻辑就完成了,就像平时使用vuex一样进行管理即可。当然主、微应用通信的这一份状态最好是单独进行维护的,不应该和微应用内部业务逻辑状态相关联,我们可以使用vuex的modules功能进行实现,这里就不再赘诉有需要再更新。
3.2 react微应用
react的实现逻辑和vue都一样,只是不同技术栈代码写法不一样而已,react中我们使用mobx、mobx-react进行状态管理。mobx开箱即用支持装饰器语法,天然存在状态隔离,和react-hook完美搭配。
第一部新建store文件:
import { observable, computed, action, reaction } from 'mobx'
import _ from 'lodash'
import { MainStoreModel } from '../model/mainApp'
class MainApp {
static setGlobalState: Function
@observable preStore: MainStoreModel = {}
@observable store: MainStoreModel = {}
constructor() {}
@computed get iptValue() {
return this.store.iptValue
}
@action
initStore(data: MainStoreModel) {
this.store = data
}
@action
setStore(data: any) {
const prev = _.cloneDeep(this.store)
this.store = {
...prev,
...data
}
}
}
const mainApp = new MainApp()
export {
mainApp,
MainApp
}
第二步修改入口文件,初始化state同步逻辑
import { mainApp, MainApp } from './store/mainApp'
/**
* 初始化主微应用通信逻辑
* 1.主应用状态变更同步到微应用;
* 2.微应用状态变更同步到主应用;
*/
function initStore(props: any) {
// 将setGlobalState方法绑定到类MainApp静态属性上
MainApp.setGlobalState = props.setGlobalState ? props.setGlobalState : null
// 主应用状态变更同步到微应用
props.onGlobalStateChange &&
props.onGlobalStateChange(
(state: MainStoreModel, prev: MainStoreModel) => {
mainApp.initStore(state)
console.log('react-app onGlobalStateChange', state, prev)
}, true
)
}
export async function mount(props: any) {
render(props)
+ initStore(props)
}
主应用状态同步到微应用逻辑和vue大同小异。不同的是,我们将setGlobalState方法绑定在MainApp类的静态属性上,微应用状态同步到主应用时会用到。
第三步reaction同步微应用状态到主应用
我们使用mobx中reaction的特性用于监听store的变化,来实现我们需要的逻辑。
class MainApp { ...
appReaction = reaction(
() => this.store,
store => {
const nextState = _.cloneDeep(store)
const prevState = _.cloneDeep(this.preStore)
if(JSON.stringify(nextState) !== JSON.stringify(prevState)) {
MainApp.setGlobalState&&MainApp.setGlobalState(nextState)
this.preStore = nextState
}
}
)
}
同步逻辑同样需要比较变更前后两份深拷贝状态的差异,防止陷入死循环。