Aiden's blog Aiden's blog
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • JavaScript教程
    • JavaScript高级程序设计
    • ES6 教程
    • Vue
    • Vue3.0
    • React
    • TypeScript 从零实现 axios
    • Git
    • TypeScript
    • JS设计模式总结
    • 小程序
    • 小程序云开发
    • Echarts
    • 微前端
    • H5
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 搜索引擎
  • ES系列
  • 经典面试题
  • 知识点总结
  • uni-app
  • 算法
  • Vue3实战
  • 小程序chatgpt
  • 小程序配网流程
  • 程序WIFI配网
  • 小程序WebSocket
  • H5 WebSocket
  • H5 TTS
  • Vue3实现OSS存储
  • 大文件分片上传
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Aiden Wu

前端界的小学生
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • JavaScript教程
    • JavaScript高级程序设计
    • ES6 教程
    • Vue
    • Vue3.0
    • React
    • TypeScript 从零实现 axios
    • Git
    • TypeScript
    • JS设计模式总结
    • 小程序
    • 小程序云开发
    • Echarts
    • 微前端
    • H5
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 搜索引擎
  • ES系列
  • 经典面试题
  • 知识点总结
  • uni-app
  • 算法
  • Vue3实战
  • 小程序chatgpt
  • 小程序配网流程
  • 程序WIFI配网
  • 小程序WebSocket
  • H5 WebSocket
  • H5 TTS
  • Vue3实现OSS存储
  • 大文件分片上传
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript文章

  • Vue文章

  • 学习笔记

    • JavaScript教程
    • JavaScript高级程序设计
    • ES6 教程
    • Vue
    • React
    • TypeScript 从零实现 axios
    • Git
    • TypeScript笔记
    • 小程序笔记
    • 小程序云开发
    • Vue3
    • JS设计模式总结笔记
    • Echarts
    • 微前端
      • qiankun
        • 主应用
        • 微应用
        • Actions通信
    • H5
  • 前端
  • 学习笔记
wushengxin
2021-10-11
目录

微前端

# qiankun

qiankun 是一个基于 single-spa (opens new window) 的微前端 (opens new window)实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。解决single-spa样式隔离、js沙箱等问题。

微前端架构具备以下几个核心价值:

  • 技术栈无关 主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级

    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时 每个微应用之间状态隔离,运行时状态不共享

# 主应用

主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可。

先安装 qiankun :

$ yarn add qiankun # 或者 npm i qiankun -S
1

注册微应用并启动:

// 注册微应用
import { registerMicroApps,start } from 'qiankun';

const apps = [
  {
    name: 'vueApp',
    entry: '//localhost:3000',
    container: '#vue',
    activeRule: '/vue',
    props:{ vueName:'小明',age:18 }
  },
  {
    name: 'vueplusApp',
    entry: '//localhost:10000',
    container: '#vueplus',
    activeRule: '/vueplus',
    props:{ vueName:'小红',age:18 }
  },
]

registerMicroApps(apps)
start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

主应用的路由配置:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    redirect: '/login',
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue')
  },
  {
    path: '/home',
    name: 'Home',
    component: () => import('../views/Home.vue'),
    redirect: '/hello',
    children: [
      {
        path: '/hello',
        name: 'Hello',
        component: () => import('../components/HelloWorld.vue')
      },
	  {
		 path: "/vue",
		 name: "vue",
         children:[
          {
            path:'/vue/*',
          },
         ]
	   },
	   {
		  path: "/vueplus",
		  name: "vueplus",
         children:[
          {
            path:'/vueplus/*',
          },
         ]
	    },
	  ],
  },
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

搭建基地,并设置微应用容器显示的位置:

// home.vue
<template>
  <div class="home">
    <el-container>
      <el-aside width="200px"
        ><el-menu
          class="el-menu-vertical-demo"
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b"
          :router="true"
        >
          <el-menu-item index="/vue"><i class="el-icon-s-grid"></i>vue应用</el-menu-item>
          <el-menu-item index="/vueplus"><i class="el-icon-s-grid"></i>vueplus应用</el-menu-item>
        </el-menu></el-aside
      >
      <el-main
        ><router-view />
        <div id="vue"></div>
        <div id="vueplus"></div>
        </el-main>
    </el-container>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 微应用

微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用(主要是指 Vue、React、Angular)需要做的事情有:

  1. 新增 public-path.js 文件,用于修改运行时的 publicPath。什么是运行时的 publicPath ? (opens new window)。

注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。

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

主要的修改就是以上四个,可能会根据项目的不同情况而改变。例如,你的项目是 index.html 和其他的所有文件分开部署的,说明你们已经将构建时的 publicPath 设置为了完整路径,则不用修改运行时的 publicPath (第一步操作可省)。

无 webpack 构建的微应用直接将 lifecycles 挂载到 window 上即可。

# Vue 微应用

以 vue-cli 3+ 生成的 vue 2.x 项目为例,vue 3 版本等稳定后再补充。

  1. 在 src 目录新增 public-path.js:

    if (window.__POWERED_BY_QIANKUN__) {
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
    1
    2
    3
  2. router/index.js

    const router = new VueRouter({
      mode: 'history',
      base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
      routes
    })
    
    1
    2
    3
    4
    5
  3. 入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围。

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import "./public-path";
    import actions from "@/shared/actions";
    
    Vue.config.productionTip = false
    
    let instance = null
    function render(props = {}){
      if (props) {
        // 注入 actions 实例
        actions.setActions(props);
      }
    
      const { container } = props;
      instance = new Vue({
        router,
        render: h => h(App)
      }).$mount(container ? container.querySelector('#app') : '#app')
    }
    
    // 独立运行时
    if (!window.__POWERED_BY_QIANKUN__) {
      render();
    }
    
    // 向主应用暴露三个生命周期钩子函数,必须是promise函数
    /**
     * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
     * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
     */
    export async function bootstrap(props) {
      console.log('[vue] vue app bootstraped');
      Vue.prototype.$vueName = props.vueName
      Vue.prototype.$age = props.age
    }
    /**
     * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
     */
    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;
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
  4. 打包配置修改(vue.config.js):

module.exports = {
    devServer:{
        port:3000,
        // 关闭主机检查,使微应用可以被 fetch
        disableHostCheck: true,
        // 允许跨域
        headers:{
            'Access-Control-Allow-Origin':'*'
        }
    },
    configureWebpack:{
        output:{
            library:'vueApp',
            libraryTarget:'umd',
            jsonpFunction: `webpackJsonp_vueApp`
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Actions通信

官方提供的应用间通信方式 - Actions 通信,这种通信方式比较适合业务划分清晰,应用间通信较少的微前端应用场景。

# 通信原理

qiankun 内部提供了 initGlobalState 方法用于注册 MicroAppStateActions 实例用于通信,该实例有三个方法,分别是:

  • setGlobalState:设置 globalState - 设置新的值时,内部将执行 浅检查,如果检查到 globalState 发生改变则触发通知,通知到所有的 观察者 函数。
  • onGlobalStateChange:注册 观察者 函数 - 响应 globalState 变化,在 globalState 发生改变时触发该 观察者 函数。
  • offGlobalStateChange:取消 观察者 函数 - 该实例不再响应 globalState 变化。

主应用

首先,我们在主应用中注册一个 MicroAppStateActions 实例并导出,代码实现如下:

// qiankun-base/src/shared/actions.ts
import { initGlobalState } from "qiankun";

const initialState = {};
const actions = initGlobalState(initialState);

export default actions;

1
2
3
4
5
6
7
8

在注册 MicroAppStateActions 实例后,我们在需要通信的组件中使用该实例,并注册 观察者 函数,我们这里以登录功能为例,实现如下:

// qiankun-base/src/views/Login.vue
import actions from "@/shared/actions";

export default {
  data() {
    return {};
  },
  mounted() {
    // 注册一个观察者函数
    actions.onGlobalStateChange((state, prevState) => {
      // state: 变更后的状态; prevState: 变更前的状态
      console.log("主应用观察者:token 改变前的值为 ", prevState.token);
      console.log(
        "主应用观察者:登录状态发生改变,改变后的 token 的值为 ",
        state.token
      );
    });
  },
  methods: {
    login() {
      // 登录成功后,设置 token
      actions.setGlobalState({ token: "dasdasbfsdf" });
      this.$router.push("/home");
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

微应用

我们已经完成了主应用的登录功能,将 token 信息记录在了 globalState 中。现在,我们进入子应用,使用 token 获取用户信息并展示在页面中。

我们首先来改造我们的 Vue 子应用,首先我们设置一个 Actions 实例,代码实现如下:

// qiankun-vue/src/shared/actions.vue
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;
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

我们创建 actions 实例后,我们需要为其注入真实 Actions。我们在入口文件 main.js 的 render 函数中注入,代码实现如下:

// qiankun-vue/src/main.js
//...

/**
 * 渲染函数
 * 主应用生命周期钩子中运行/子应用单独启动时运行
 */
function render(props) {
  if (props) {
    // 注入 actions 实例
    actions.setActions(props);
  }

  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
    mode: "history",
    routes,
  });

  // 挂载应用
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount("#app");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

从上面的代码可以看出,挂载子应用时将会调用 render 方法,我们在 render 方法中将主应用的 actions 实例注入即可。

最后我们在子应用的 通讯页 获取 globalState 中的 token,使用 token 来获取用户信息,最后在页面中显示用户信息。代码实现如下:

// 引入 actions 实例
import actions from "@/shared/actions";
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      token: "",
    };
  },
  mounted() {
    // 注册观察者函数
    // onGlobalStateChange 第二个参数为 true,表示立即执行一次观察者函数
    actions.onGlobalStateChange((state) => {
      const { token } = state;
      console.log(token);
      this.token = token;
      // 未登录 - 返回主页
      if (!token) {
        console.log("未检测到登录信息!");
        window.location.href = "http://192.169.1.151:8080";
      }
    }, true);
  },
  methods: {
    // 在主应用的某个组件中用setGlobalState更改state值
    send() {
      let token = "小明修改了token";
      actions.setGlobalState({ token });
    },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

从上面的代码可以看到,我们在组件挂载时注册了一个 观察者 函数并立即执行,从 globalState/state 中获取 token,然后使用 token 获取用户信息,最终渲染在页面中。

编辑 (opens new window)
上次更新: 2021/12/06, 21:28:32
Echarts
H5

← Echarts H5→

最近更新
01
大文件分片上传
08-05
02
Vue3实现OSS存储
08-05
03
H5 TTS
08-05
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Aiden Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×