vue+qiankun微前端项目(搭建,路由跳转,父子之间相互通信)

话不多说,直接上货

首先创建两个项目,一个作为项目基座(vue-qiankun-base),一个作为子应用(vue-qiankun-app1)

搭建基座及配置

安装qiankun依赖到基座

npm i qiankun -S

需要注意的几点:

1.新增 public-path.js 文件,用于修改运行时的 publicPath 什么是运行时的 publicPath ?
2.微应用建议使用 history 模式的路由,需要设置路由 base,值和它的 activeRule 是一样的。
3.在入口文件最顶部引入 public-path.js ,修改并导出三个生命周期函数。
4.修改 webpack 打包,允许开发环境跨域和 umd 打包。

配置基座
1.注册微应用并启动:
在mani中导入并启用

import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:3000',
    container: '#app1',// 子应用的id
    activeRule: '/app1',
  },
]);
// 启动 qiankun
start();

在app中加入相对应的app1

2.基座添加路由
router.js

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const routes = [];
const router = new VueRouter({
  mode: 'history',
  routes,
});
export default router

main.js中导入并挂载到vue的实例上

import router from './router';
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

路由跳转 >>> 因为只是介绍使用方式,故就在App.vue中写入跳转方式

Cat Dog

vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    username: ''
  },
  getters: {
  },
  mutations: {
    changeName(state, username){
      state.username = username;
    }
  },
  actions: {
    changeName(context, username){
      context.commit('changeName', username);
    }
  },
  modules: {
  }
})

initGlobalState(state)
定义全局状态,并返回通信方法,建议在基座使用,子应用通过 props 获取通信方法。

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

import { initGlobalState, MicroAppStateActions } from 'qiankun';

// 注意放到start()下面
const state = {
  username: '方骈臻'
}
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);// 初始化了state
// actions.offGlobalStateChange();
store.dispatch('changeName', state.username);// 修改vuex中值

App.vue中

{{ $store.state }}
基座 (Cat    Dog



配置子应用
1.在 src 目录新增 public-path.js:(主要作用是补全路径,浏览器有跨域的问题,需要拿到完整的url(特指图片等文件))

/*eslint disable no undef*/
// 上方这一行用于eslint的忽略,因为下方代码的指向其实是不存在的,在有eslint的环境中不加入此行会报错
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

将此文件在main.js头部引入

import './public-path';

2.入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。

let instance = null;
function render(props = {}) {
  const { container } = props;

  instance = new Vue({
    store,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
}

3.打包配置修改(vue.config.js):

const { name } = require('./package');
module.exports = {
  devServer: {
    port: 82,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,// 取决于webpack的版本(可不要,但小于webpack5.x则需要加上)
    },
  },
};

此时基础的框架已经搭建完毕,可以显示子应用了,我们再来配置路由
首先是在src下创建router目录,并在其中创建index.js,响应的文件也创建完成

import Vue from "vue";
import Cat from '../pages/Cat';
import Dog from '../pages/Dog';

import VueRouter from 'vue-router'
Vue.use(VueRouter)

const routes = [
  {
    path: '/cat',
    component: Cat
  },{
    path: '/dog',
    component: Dog
  },
];
export default routes

再在main.js中改写为如下

import './public-path';
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'// 引入路由
import routes from './router'// 引入路由文件
Vue.config.productionTip = false

let router = null;// 创建一个空值
let instance = null;
function render(props = {}) {
  const { container } = props;

  // 给该值赋值。注意,上面提到过,子组件中需使用history模式
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/', // 注意此处的名字要与基座中对应
    mode: 'history',
    routes,
  });

  instance = new Vue({
    router,// 将路由挂在到vue上
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
  console.log('[vue] props from main framework', props);
  render(props);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;// 释放路由
}

路由跳转 >>> 因为只是介绍使用方式,故就在App.vue中写入跳转方式
注:子应用中不需要加入/app1的前缀可独立运行

Cat Dog

vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    username: ''
  },
  getters: {
  },
  mutations: {
    changeName(state, username){
      state.username = username;
    }
  },
  actions: {
    changeName(context, username){
      context.commit('changeName', username);
    }
  },
  modules: {
  }
})

main.js

import store from './store'

let router = null;
let instance = null;

// 调用基座vuex数据的方法
function storeTest(props) {
    //props.onGlobalStateChange &&
    // props.onGlobalStateChange(
    //  (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
    //  true,
    //);
   props.onGlobalStateChange &&
      props.onGlobalStateChange(
        (value, prev) => {
          store.dispatch('changeName', value.username);
        },
        true,
      );
    props.setGlobalState &&
      props.setGlobalState({
        ignore: props.name,
        user:{
          name: props.name
        }
      })
}

function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/',
    mode: 'history',
    routes,
  });
  console.log(props);// 打印从基座拿到的数据
  instance = new Vue({
    router,
    store, // 一定是挂载到qiankun的方法里面来
    render: (h) => h(App),
  }).$mount(container ? container.querySelector('#app') : '#app');
}

// 此处使用
export async function mount(props) {
    console.log('[vue] props from main framework', props);
    storeTest(props)
    render(props);
}

App.vue

{{ $store.state }}
子应用 Cat    Dog



基座与子应用之间的数据互通

基座
main.js

// 如果要在页面中使用,可以挂在到原型链上
// 也可以将需要通信的数据在vuex中进行操作,此处便不再赘述
Vue.prototype.$setGlobalState = actions.setGlobalState;

actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
  // 监听子应用数据更改
});

App.vue


子应用
main.js

function storeTest(props) {
    // 如果改方法存在,则能拿到对应的数据
    // props.onGlobalStateChange &&
    //   props.onGlobalStateChange(
    //     (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
    //     true,
    //   );
    props.onGlobalStateChange &&
      props.onGlobalStateChange(
        (value, prev) => {
          store.dispatch('changeName', value.username);
        },
        true,
      );
    // props.setGlobalState &&
    //   props.setGlobalState({
    //     ignore: props.name,
    //     user:{
    //       name: props.name
    //     }
    //   })
    if(props.setGlobalState){
        Vue.prototype.$setGlobalState = props.setGlobalState; // 同基座,将set方法加载到原型链上
    }
}

App.vue

{{ $store.state }}
子应用 (Cat    Dog