微前端实践之vue3使用qiankun(三)相互通信

1,717 阅读4分钟

互相通信,主-子,子-主,子-子

(1)方式一,使用官方的initGlobalState来实现

官方使用教程中,只是在mount的生命周期里去调用该方法,在实际的项目中需要在其他页面中调用全局参数,所以我们通过写一个actions.js将这个全局状态获取出来,方便在各个地方使用。这里参考的是基于qiankun的微前端最佳实践(图文并茂)对actions的封装。

首先在主应用创建actions.js,将全局状态实例导出来(导出来是为了在主应用自己使用)

import { initGlobalState, MicroAppStateActions } from 'qiankun';

const initialState = {};
const actions = initGlobalState(initialState);//定义全局状态

export default actions;

使用实例的方法,该实例有以下几个方法,可以设置,获取全局状态。

  • onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
  • setGlobalState: (state: Record<string, any>) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
  • offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用

\

\

onGlobalStateChange在每一次全局状态改变的时候都会调用, 第二个参数为 true,表示立即执行一次观察者函数

主应用设置,主应用获取

import actions from '../shared/actions';
...
const userInfo = {
  userName: '黄马泥',
  token: '123456789',
  userId: '1',
  role: 'admin',
};//微应用中只能修改已存在的一级属性,也就是这里定义的

//设置全局状态
actions.setGlobalState(userInfo);

//获取全局状态
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});

主应用设置,子应用获取,子应用修改,主应用和其他子应用获取。

主应用的配置就是上面配置,如果子应用需要使用,按照以下配置

首先创建action.js文件

// micro-app-vue/src/shared/actions.js
function emptyAction() {
  // 警告:提示当前使用的是空 Action
  console.warn('Current execute action is empty!');
}

class Actions {
  // 默认值为空 Action
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction,
  };

  /**
   * 设置 actions
   */
  setActions(actions) {
    this.actions = actions;
  }

  /**
   * 映射
   */
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args);
  }

  /**
   * 映射
   */
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args);
  }
}

const actions = new Actions();
export default actions;

然后获取主应用的实例,在render中通过 actions.setActions(props)获取

这里解释下,为什么没有在主应用的props中传入actions对象,这里就能获取。因为initGlobalState调用以后,在render打印出 props会发现,props里自己就会有了onGlobalStateChange和setGlobalState,所以在上述actions.js的封装里,我们直接就把这两个属性映射给了 actions 对象。


function render(props = {}) {
  console.log(props);
  actions.setActions(props); //获取主应用的全局状态实例
  const { container } = props; 
  instance = createApp(App);
  instance
    .use(store)
    .use(router)
    .use(Antd)
    .mount(container ? container.querySelector('#app') : '#app');

  instance.config.globalProperties.mainRouter = props.router;

  console.log('主应用rouer', props.router);
}

接下来就可以使用这个对象进行获取和设置的操作了,在子应用的VUE页面下就可以这么写,随后调用changeName就可以改变主应用和其他所有子应用中的userName值了。

调用了setGlobalState方法后,其他所有 主应用 子应用 中的onGlobalStateChange方法都会调用一次,这样就可以同步到所有的应用中了。但是必须 主应用先进行第一次设置

setup(){
  const userName = ref({});
  let globalState = '';
  
  //获取全局状态
  actions.onGlobalStateChange((state) => {
      //  console.log(state);
      console.log(state.userName);
      userName.value = state.userName;
      globalState = state;
	}, true);第二个参数为 true,表示立即执行一次观察者函数

  //修改全局状态
  const changeName = ()=>{
     globalState.userName = '王马泥';
    
 		 actions.setGlobalState(globalState);
  }
 
  return{
    userName,
    changeName
  }
}

(2)方式二,使用主应用的vuex实现(演示代码为vue2.x)

实现思路:在主应用创建vuex,通过props把vuex传递到子应用中,同时子应用通过Vue. prototype.xxx全局变量的方式定义自己的vuex,这样就可以不影响 子应用 使用vuex。

实现步骤

1.创建主应用的vuex,通过store/index.js 创建

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
    
    state: {
        // 定义一个name,以供全局使用
        name: '',
        // 定义一个number,以供全局使用
        number: 0,
        // 定义一个list,以供全局使用
        list: [
            { id: 1, name: '111' },
            { id: 2, name: '222' },
            { id: 3, name: '333' },
        ]
    },
    mutations: { // 增加nutations属性
        setName(state , name) {  // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数
            state.name = name;
        }
    },

});

export default store;

2、通过props把vuex传递到子应用中

 {
    name: "vueApp", // 应用的名字
    entry: "http://localhost:10000/", //
    container: "#vue", // 要渲染到的容器名id
    activeRule: "/vue", // 通过哪一个路由来激活
    props:{
      store
    }//参数
  },

3、子应用中使用

首先从render中获取,然后设置自己的store,取名为microStore

这里注意设置Vue.observable响应式

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import Vuex from 'vuex'
import  microStore from './store'
Vue.prototype.microStore = microStore;
Vue.config.productionTip = false
Vue.use(Vuex);


let instance = null;
function render(props) {

   const store = props.store;//主应用的store
   Vue.observable(store);//将共享store设置为响应式,否则子应用的store中的值不会改变
 
  // props 组件通信
  instance = new Vue({
    router,
    store,
    render: h => h(App)
  }).$mount('#app') // 这里是挂载到自己的HTML中,基座会拿到这个挂载后的HTML,将其插入进去
}

if (!window.__POWERED_BY_QIANKUN__) { // 如果是独立运行,则手动调用渲染
  render();
}
if(window.__POWERED_BY_QIANKUN__){ // 如果是qiankun使用到了,则会动态注入路径
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// 根据 qiankun 的协议需要导出 bootstrap/mount/unmount
export async function bootstrap(props) {

};
export async function mount(props) {
  render(props);
};
export async function unmount(props) {
  instance.$destroy();
};

在vue页面中调用

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
     <div class="head">主应用store:{{$store.state.name}}</div>
      <div>自己的store:{{microStore.state.name}}</div>
    <button @click="logout">登出</button>
  </div>
</template>

<script>
// @ is an alias to /src
export default {

  methods:{
    logout(){
      this.$store.commit('setName','王七八九');
   
    }
  }
}
</script>
<style scoped>
  .head{
    background-color: green;
    color: white;
  }
</style>

总结

到目前为止,用qiankun搭建了一个基本的框架,实现了主子应用之间的通信,互相的跳转

\

其中我并没有集成一个common,因为我觉得这违背的微前端的解耦性质,官方也有提到提取公共依赖的问题,需要的话可以去查看