# qiankunExample **Repository Path**: company-test_0/qiankunExample ## Basic Information - **Project Name**: qiankunExample - **Description**: qiankun - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-12-09 - **Last Updated**: 2024-05-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # qiankunExample ## 参考链接 * [qiankun中文文档](https://qiankun.umijs.org/zh/faq) * [qiankun源码与umi基座样例](https://github.com/umijs/qiankun) * [qiankun教程](https://www.jianshu.com/p/a5d9c53abde3) * [基于 qiankun 的微前端最佳实践(图文并茂) - 应用间通信篇](https://blog.csdn.net/qq_34621851/article/details/106003110) * [qiankun通信样例代码](https://github.com/a1029563229/micro-front-template/tree/feature-communication) * [Vue+微前端(QianKun)落地实施和最后部署上线总结(二)普通版](https://juejin.cn/post/7041151571467436063) * [qiankun-微应用生命周期钩子](https://blog.csdn.net/weixin_44123848/article/details/118679042) * [从零手写微前端qiankun框架【超详细万字长文】](https://juejin.cn/post/7248951438742224951) ## 目录 * [vue3基座样例](vue3基座样例) * [应用通讯](#应用通讯) * [Actions通信](#Actions通信) * [Shared通信](#Shared通信) * [部署](#部署) * [双端口部署](#双端口部署) * [单端口部署](#单端口部署) --- ## vue3基座样例 把官方vue3子应用样例复制一份作为基座 基座main.js的qiankun部分同umi样例,修改注册微应用部分和默认进入的子应用 vue3子应用及其它子应用(含mount/unmount/bootstrap/打包配置)与官方样例完全一致 配好router和store ```js import { createRouter, createWebHistory } from 'vue-router'; import routes from './router'; import store from './store'; let app = createApp(App); let history = createWebHistory('/'); let router = createRouter({ history, routes, }); app.use(router); app.use(store); app.mount('#app'); ``` 根目录package.json保留cross-env用作purehtml运行和npm-run-all并发运行所有应用 如有缺包,可执行加入package.json 如有报错,见[参考链接](#参考链接)1 ## 应用通讯 ### Actions通信 * 通信原理 qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是: 1. setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。 2. onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。 3. offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。 * 优点 1. 使用简单 2. 官方支持性高 3. 适合通信较少的业务场景 * 缺点 1. 子应用独立运行时,需要额外配置无 Actions 时的逻辑; 2. 子应用需要先了解状态池的细节,再进行通信; 3. 由于状态池无法跟踪,通信场景较多时,容易出现状态混乱、维护困难等问题。 * 使用 1. 主应用 /src/shared/actions.js初始化全局状态 ```js import { initGlobalState } from "qiankun"; const initialState = {}; const actions = initGlobalState(initialState); export default actions; ``` 以App.vue页面为例,点击登录按钮修改全局token并监听变化 ```html ``` 2. 子应用 /src/shared/actions.js用于承载父应用传过来的onGlobalStateChange和setGlobalState ```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; ``` main.js在render时传入props ```js function render(props = {}) { if (props) { // 注入 actions 实例 actions.setActions(props); } ... } ``` 以App.vue页面为例,获取token ```html ``` * 注意 * 父子应用对同一全局变量进行setGlobalState和onGlobalStateChange导致循环调用 1. 可加入if判断,防止两边循环赋值 2. 可只在父应用setGlobalState,子应用onGlobalStateChange 3. 父子应用使用不同变量 * 示例示范了父应用设置,子应用监听,反之亦然 ### Shared通信 * 通信原理 主应用基于 redux 维护一个状态池,通过 shared 实例暴露一些方法给子应用使用。同时,子应用需要单独维护一份 shared 实例,在独立运行时使用自身的 shared 实例,在嵌入主应用时使用主应用的 shared 实例,这样就可以保证在使用和表现上的一致性。 * 优点 1. 可以使用市面上比较成熟的状态管理工具,如 redux、mobx,可以有更好的状态管理追踪和一些工具集。 2. 可以帮助主应用更好的管控子应用。子应用只可以通过 shared 实例来操作状态池,可以避免子应用对状态池随意操作引发的一系列问题。 3. 适合通信较少的业务场景 * 缺点 1. 通信方案需要自行维护状态池,这样会增加项目的复杂度。 2. 通信方案要求父子应用都各自维护一份属于自己的 shared 实例,同样会增加项目的复杂度。 3. 子应用可以完全独立于父应用运行(不依赖状态池),子应用也能以最小的改动被嵌入到其他 第三方应用 中。 * 为什么shared通信需要redux而不能是vuex? redux和vuex都是状态管理器,vuex吸取了redux的优点又舍弃了部分特性,导致vuex只适配于vue,action和mutation取代了reducer,因此使用redux能够适用于所有框架。 * 使用 1. 主应用 /src/shared/store.js创建redux ```js import { createStore } from "redux"; const reducer = (state = {}, action) => { switch (action.type) { default: return state; // 设置 Token case "SET_TOKEN": return { ...state, token: action.payload, }; } }; const store = createStore(reducer); export default store; ``` /src/shared/index.js定义共享对象,调用redux方法 ```js import store from "./store"; class Shared { /** * 获取 Token */ getToken() { const state = store.getState(); return state.token || ""; } /** * 设置 Token */ setToken(token) { // 将 token 的值记录在 store 中 store.dispatch({ type: "SET_TOKEN", payload: token }); } } const shared = new Shared(); export default shared; ``` /src/views/About.vue调用实例的set ```js import { onMounted } from "vue"; import shared from "../shared/index"; export default { name: "About", setup() { const login = () => { shared.setToken("reduxToken"); }; return { login }; }, }; ``` main.js传递props ```js { name: 'vue3', entry: '//localhost:7105', container: '#subapp-viewport', activeRule: '/vue3', // 通过 props 将 shared 传递给子应用 props: { shared } }, { name: 'purehtml', entry: '//localhost:7104', container: '#subapp-viewport', activeRule: '/purehtml', // 通过 props 将 shared 传递给子应用 props: { shared } } ``` 2. 子应用 /src/shared/index.js子应用共享类,承载父应用实例或使用自身方法 ```js class Shared { /** * 获取 Token */ getToken() { // 子应用独立运行时,在 localStorage 中获取 token return localStorage.getItem("token") || ""; } /** * 设置 Token */ setToken(token) { // 子应用独立运行时,在 localStorage 中设置 token localStorage.setItem("token", token); } } class SharedModule { static shared = new Shared(); /** * 重载 shared */ static overloadShared(shared) { SharedModule.shared = shared; } /** * 获取 shared 实例 */ static getShared() { return SharedModule.shared; } } export default SharedModule; ``` main.js在render时重载父应用方法,如props无传过来的方法,则使用自身方法 ```js function render(props = {}) { // 当传入的 shared 为空时,使用子应用自身的 shared // 当传入的 shared 不为空时,主应用传入的 shared 将会重载子应用的 shared const { shared = SharedModule.getShared() } = props; SharedModule.overloadShared(shared); ... } ``` 以/src/views/About.vue为例get ```js import { onMounted } from "vue"; import SharedModule from "../shared/index"; export default { name: "About", setup() { let shared; onMounted(() => { shared = SharedModule.getShared(); // 使用 shared 获取 token const token = shared.getToken(); console.log('receive redux token',token); }); const changeToken = () => { shared.setToken('redux vue3 token'); } return { changeToken } }, }; ``` * 注意 * 示例示范了父应用set,传递props,子应用get,反之,子应用与父应用通过props共享实例后,子应用set,父应用get即可 ## 部署 需要对主应用和子应用分别build ### 双端口部署 端口A部署主应用,端口B部署所有子应用且设置支持cors。 * 缺点:需要两个端口 * 优点:解决页面刷新直接跳到子应用 ```conf # 主应用 9001 { listen 9001; location / { root /data/front-end/front-micro/micro_main; index index.html; try_files $uri $uri/ /index.html; expires -1; add_header Cache-Control no-cache; } } # 所有子应用 9002 { listen 9002; # cors add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type '; add_header 'Access-Control-Allow-Credentials' 'true'; # 子应用A location /module/micro_A { index index.html; try_files $uri $uri/ /micro_A/index.html; alias /data/front-end/front-micro/micro_A; expires -1; add_header Cache-Control no-cache; } # 子应用B location /module/micro_B { index index.html; try_files $uri $uri/ /micro_B/index.html; alias /data/front-end/front-micro/micro_B; expires -1; add_header Cache-Control no-cache; } # 子应用C location /module/micro_C { index index.html; try_files $uri $uri/ /micro_C/index.html; alias /data/front-end/front-micro/micro_C; expires -1; add_header Cache-Control no-cache; } } ``` ### 单端口部署 所有应用都部署在一个端口上,不会存在跨域 * 优点:只需管理一个端口 * 缺点:页面刷新直接跳到子应用 ```conf { listen 9001; # 主应用 location / { root /data/front-end/front-micro/micro_main; index index.html; try_files $uri $uri/ /index.html; expires -1; add_header Cache-Control no-cache; } # 子应用A location /module/micro_A { index index.html; try_files $uri $uri/ /micro_A/index.html; alias /data/front-end/front-micro/micro_A; expires -1; add_header Cache-Control no-cache; } # 子应用B location /module/micro_B { index index.html; try_files $uri $uri/ /micro_B/index.html; alias /data/front-end/front-micro/micro_B; expires -1; add_header Cache-Control no-cache; } # 子应用C location /module/micro_C { index index.html; try_files $uri $uri/ /micro_C/index.html; alias /data/front-end/front-micro/micro_C; expires -1; add_header Cache-Control no-cache; } } ```