微前端

什么是微前端?

在讲解微前端之前先给大家看个示例(https://www.ucloud.cn/):
image

UCloud是一个云计算服务平台,是比较典型的中后台应用,像这种应用往往面临着当业务发展之后,由刚刚开始的单体应用进化成巨石应用的问题,比如UCloud有很多的产品,如下图
image如果按照我们通常的开发单页面后台应用的规则去开发的话,往往会面临几个问题:

  • 毫无相关的业务会使应用越来越大,工程越大,从而导致越来越难以维护的问题。
  • 团队人员多,产品功能复杂,代码冲突频繁、影响面大。
  • 在不少的业务中,或多或少会存在一些历史稳定的项目,但是这些项目可能使用比较老框架,介于日常运营,这些系统需要结合到新框架中来使用还不能抛弃,对此我们也没有理由浪费时间和精力重写旧的逻辑。

为解决以上问题,我们引入微前端的概念:
那么到底什么是微前端,借助qiankun官网的说法,微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。简单来说将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品

微前端的价值

我们先看一个基本的微前端框架构成,微前端由主应用和子应用构成,主应用就是最外面的框架,子应用可以是任何语言来写,可以单独运行,也可以放到主框架中进行运行
image

根据上图可知,微前端架构具备以下几个核心价值:

  • 技术栈无关

    主框架不限制接入应用的技术栈,可以随意选择vue,react,angular等框架接入其中

  • 独立开发、独立部署

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

  • 增量升级

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

  • 独立运行时

    每个微应用之间状态隔离,运行时状态不共享,将巨石应用拆解成若干可以自治的松耦合微应用

引入微前端框架qiankun

官网地址:https://qiankun.umijs.org/zh/guide

简介:qiankun 是蚂蚁金服基于single-spa 的微前端解决方案

qiankun使用(基于vue)
主框架的挂载乾坤

我们借用vue的hash模式进行开发

//main.js文件

import Vue from "vue";
import App from "@/App.vue";
import store from "@/store";
import router from "@/router";
import setupQk from "@/setup/qiankun/index"

new Vue({
  store,
  router,
  render: h => h(App)
}).$mount("#app");

setupQk() //安装乾坤

除了上面基本的路由,vuex配置,我们主要看下setupQk,主要安装逻辑在这里

// @/setup/qiankun/index.js文件
import microAppConfig from "./config.js"; //此文件代码在下方
import {start,registerMicroApps,initGlobalState} from "qiankun";


//乾坤配置  https://qiankun.umijs.org/zh/api
const qkConfig = {
  sandbox: { strictStyleIsolation: true } //开启shadow dom沙箱隔离
}
let actions =null;

/**
 * @description:  添加监测状态
 */
function addState(state) {
   if(!actions){
      /**
     * @description:  乾坤初始化全局监听状态
     * @param state要初始化的值
     */
    actions = initGlobalState(state);
    actions.onGlobalStateChange(
      (value, prev) => {;
        console.log(`[主应用接收到值变化 - ${actions}]:`, value, prev);
      }
    );
   }else{
     /**
     * @description:  乾坤设置state值
     * @param state要更新的值
     */
    actions.setGlobalState(state);
   }
}

/**
 * @description:  注册子应用
 */
function registerApp(){

  /**
   * @description:  乾坤注册子应用
   * @param1 子应用配置
   * @param2 打开子应用时候触发的生命周期
   */
  registerMicroApps(microAppConfig, {
    beforeLoad: [
      app => {
        console.log("[主应用] before load %c%s", "color: green;", app.name);
      }
    ],
    beforeMount: [
      app => {
        console.log("[主应用] before mount %c%s", "color: green;", app.name);
      }
    ],
    afterMount: [
      app => {
        console.log("[主应用] afterMount mount %c%s","color: green;",app.name);
      }
    ],
    afterUnmount: [
      app => {
        console.log("[主应用] after unmount %c%s", "color: green;", app.name);
      }
    ]
  });
}

/**
 * @description:  挂载乾坤框架
 */
function setupQk() {
  let state = {a:123}
  registerApp()
  addState(state)
   /**
   * @description:  乾坤开启服务
   * @param1 全局配置
   */
  start(qkConfig);
}

export default setupQk;



所有配置子应用的配置都在配置文件config.js中

import store from "@/store";
const loader = function(_status) {
    store.dispatch("setApplicationState", _status);
  };
const getActiveRule = hash => location => location.hash.startsWith(hash);
export default [
  {
    name: "quality", //子应用名称
    entry: " http://localhost:8084", //子应用地址,假设此项目名称为智能质检应用(下面演示生命周期的时候会用到)
    container: "#subapp-viewport", //子应用要挂载的节点,和vue.$mount("#app")类似;
    loader,//切换时候的加载动画,根据自己需求加入
    activeRule: getActiveRule("#/quality") //重点:路由命中规则,当浏览器链接有#/quality时候,例如http://localhost:9000/#/quality/* ,就可以自动的把http://localhost:808挂载到本地的subapp-viewport节点下
  },
  {
    name: "training",
    entry: "http://localhost:8081",//子应用地址,假设此项目名称为智能陪练应用
    container: "#subapp-viewport",
    loader,
    activeRule: getActiveRule("#/training")
  }
];

配置好了乾坤主应用一定要配置路由规则

//路由配置文件

import Main from '@/view/main/main.vue' //代码在下方
const appRouter = [
  {
    path: "/training/*", //重点:路由匹配规则一定要加/*,因为要匹配子应用路由
    name: "training",
    meta: {
      title: "智能陪练",
      icon: "el-icon-goods",
    },
    component: Main
  },
  {
    path: "/quality/*",
    name: "quality",
    meta: {
      title: "智能质检",
      icon: "el-icon-goods",
    },
    component: Main
  }
];
export default appRouter;

//main.vue

<template>
    <div class="header">
    ...
    </div>
    <div class="body">
        <div class="menu">
        ...
        </div>
        <!--重点:一定要写子应用挂载的容器,对应子应用的配置都在配置文件config.js中的container,否则主应用不知挂载到哪里-->
        <div class="subapp-viewport"></div> 
    </div>
</template>


   

写到这里主应用基本上已经ok了,然后我们再来写一下子应用的挂载

子应用的挂载乾坤
//main.js

import Vue from "vue";
import App from "@/App.vue";
import store from "@/store";
import router from "@/router";
import setupQk from "@/setup/qiankun/index.js";


function createApp() {
  return new Vue({
    router,
    store,
    render: h => h(App)
  });
}

if (!window.__POWERED_BY_QIANKUN__) {
  /**
   * @description:  保证非嵌套在乾坤子应用可以独立运行
   */
  createApp().$mount("#app");
} else {
  setupQk(createApp);
}

/**
 * @description:  重点:抛出子应用生命周期
 */
export { bootstrap, mount, unmount } from "@/setup/qiankun/index.js";


// @/setup/qiankun/index.js

let app = null;
let createApp = null;
let appEmitIns = null;

/**
 * @description:  监测数据变化
 */
function Monitor(props) {
  appEmitIns = props;
  appEmitIns.onGlobalStateChange((value, prev) => {
    console.log(`[陪练应用接收到值变化 - ${props.name}]:`, value, prev);
  }, true);
}

/**
 * @description:  修改应用的值,注意:子应用中只能修改已存在的一级属性
 */
export function appEmit() {
  if (appEmitIns) {
    appEmitIns.setGlobalState({
      a: 444444
    });
  }
}
/**
 * @description:  子应用初始化
 */
export async function bootstrap() {
  console.log("[陪练应用] bootstrap");
}

/**
 * @description:  子应用挂载
 */
export async function mount(props) {
  console.log("[陪练应用] mount", props);
  if (!app) {
    const { container } = props;
    app = createApp();
    app.$mount(container.querySelector("#app"));
  }
  Monitor(props);
}

/**
 * @description:  子应用卸载
 */
export async function unmount() {
  console.log("[陪练应用] unmount");
  app.$destroy();
  app.$el.innerHTML = "";
  app = null;
  appEmitIns = null;
}

/**
 * @description:  挂载乾坤子应用
 */
function setupQk(_createApp) {
  // qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
  createApp = _createApp;
}
export default setupQk;


//vue.config.js

module.exports ={
    ...
     configureWebpack: config => {
        // 把子应用打包成 umd 库格式
        config.output.library = `${name}-[name]`;
        config.output.libraryTarget = "umd";
        config.output.jsonpFunction = `webpackJsonp_${name}`;
        ...
      }
    ...
}

生命周期测试

当我第一次打开陪练项目时候,生命周期如下图

image

当我切换子应用为智能质检项目的时候,会触发下图的生命周期

image

应用之间传指测试

我们已经在主应用中设置了默认值,再次回顾下主应用代码

// /setup/qiankun/index.js

function setupQk() {
  let state = {a:123}
  registerApp();
  addState(state); //初始化为{a:123}
  start(qkConfig);
}

先看下子应用监测数据变化函数,onGlobalStateChange接受两个参数,第一个在当前应用监听全局状态,有变更触发 callback,第二个参数 true 代表立即触发 callback

/**
 * @description:  监测数据变化
 */
function Monitor(props) {
  appEmitIns = props;
  appEmitIns.onGlobalStateChange((value, prev) => {
    console.log(`[质检应用接收到值变化 - ${props.name}]:`, value, prev);
  }, true);
}

/**
 * @description:  修改应用的值,注意:子应用中只能修改已存在的一级属性
 */
export function appEmit() {
  if (appEmitIns) {
    appEmitIns.setGlobalState({
      a: 444444,
      b:333
    });
  }
}

image

当我点击页面上的测试时候会触发appEmit函数,看下效果

image

由效果可见,会先触发主应用的监听,再触发子应用的监听,但是b赋值给333是不起作用的,因为子应用中只能修改已存在的一级属性

Logo

前往低代码交流专区

更多推荐