基于qiankun微前端的通信方案

一、概念

之前的一篇文章基于qiankun从零搭建了一套微前端项目,主应用是vue,微应用包括vue、react。内部页面比较单一需要根据实际业务添砖加瓦,每个微应用应该是严格按照业务进行拆分的,但是在实际项目开发过程中,主应用、微应用之间能相互通信是基本的需求。

目前有关微前端通信的方案无非两种:

  1. 使用qiankun提供的api-initGlobalState进行通信;
  2. 通过在主、微应用中定义状态管理类的方式进行通信;

下面我们就第一种方案进行详细讲解,毕竟官方提供了实现方案。达到的效果如下:

  • 主应用作为中转,进行状态承接与派发;
  • 各个微应用不仅能获取主应用的状态变更,还能同步自身状态到主应用;
  • 微应用能独立运行;

二、主应用

我们先来看看initGlobalState(state) 这个api的具体信息:

  • 用法:在主应用中使用,定义全局状态,微应用通过props传参获取;
  • 返回:
    • 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 } } ) }

同步逻辑同样需要比较变更前后两份深拷贝状态的差异,防止陷入死循环。 

完整微前端项目代码在github上。

四、后记

到此,修改微应用中state中的值后,每个微应用中的状态都会实时更新。当然也可以在状态同步的关口增加自定义逻辑,使控制更加细粒化。

最后小伙伴有什么想法欢迎留言讨论,原创不易,看到这里的希望给个赞再走吧!!

系列相关文章:

基于qiankun从零搭建微前端项目

基于qiankun微前端项目的通信方案

原文地址:https://www.cnblogs.com/lodadssd/p/14480412.html