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

<div id="app">
    <div id="app1"></div>
</div>

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中写入跳转方式

<div id="app">
  <div id="app1"></div>
  <router-link to="/app1/cat">Cat</router-link>
  <router-link to="/app1/dog">Dog</router-link>
</div>

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<string, any>) => 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中

<div id="app">
    {{ $store.state }}
    <br/>
    基座
    (<router-link to="/app1/cat">Cat</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/app1/dog">Dog</router-link>)
    <br/>
    <br/>
    <br/>
    <div id="app1"></div>
  </div>

配置子应用
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的前缀可独立运行

<div id="app">
  <div id="app1"></div>
  <router-link to="/cat">Cat</router-link>
  <router-link to="/dog">Dog</router-link>
</div>

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

<div id="app">
  {{ $store.state }}
  <br/>
  子应用
  <router-link to="/cat">Cat</router-link>&nbsp;&nbsp;&nbsp;
  <router-link to="/dog">Dog</router-link>)
  <router-view/>
</div>



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

基座
main.js

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

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

App.vue

<script>
  export default {
    name: 'App',
    methods:{
      changeName(){
        this.$store.dispatch('changeName','fangfangfang');
        this.$setGlobalState({username:'fangfangfang'})// 将数据发送给子应用
      }
    }
  }
</script>

子应用
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

<div id="app">
    <button @click="changeName">changeName</button>
    {{ $store.state }}
    <br/>
    子应用
    (<router-link to="/cat">Cat</router-link>&nbsp;&nbsp;&nbsp;
    <router-link to="/dog">Dog</router-link>)
    <router-view/>
</div>
<script>
  export default {
    name: 'App',
    methods:{
      changeName(){
        this.$store.dispatch('changeName','child111 fangfangfang');
        this.$setGlobalState({username:'child111 fangfangfang'});// 更改主应用数据
      }
    }
  }
</script>
<style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容