【长文】微前端很好,为什么我却不使用? | 微前端原理剖析

简介: 本文记录我对微前端原理的探索与思考,以及微前端框架qiankun项目实践。

当下前端所存在的一些问题

  1. 在技术浪潮的推动下,由vue、react所主导的单页面应用已成为主流,但在开发中,随着业务的深入和项目的复杂,带来了逻辑定位问题、打包速度问题部署上线等等问题,往往我们可能只是更改了一行JS代码,到最后发布的时候,整个项目却要整个重新打包编译发布。
  2. 公司可能存在旧系统框架开发维护的项目,我们需要对以前的项目进行迭代或维护的时候,就不得不适应之前项目的开发环境,如果想要使用新技术,将会遇到阻碍。
  3. 单页面应用在应对大型项目的场景下,不可避免的会造成用户在首次进入的时候加载时间较长,因为几乎所有JS都在打包在一起,即使采用路由懒加载的技术进行优化,也依然无法避免这其中产生的DNS解析、三次握手、网络传输、代码解析等耗费的时间代价。

我们所希望的

  1. 能够使各个子模块或者子系统进行隔离。我们在开发或更新一个子模块的时候,只需要对这个子模块单独进行打包,发布上线,而不会影响到其他模块。各个子系统由于相互隔离,不会受限于技术栈的影响,更加轻量化,打包速度,前端性能等也会上去。
  2. 能够使各个子系统之间进行数据共享,例如用户信息,状态。
  3. 能够对JS,CSS等进行相互隔离,防止出现方法或样式污染问题。

微前端

微前端(Micro-Frontends)是一种类似于微服务的架构,它将微服务的理念应用于前端,即 Web 应用由单一的单页面应用转变为多个小型前端应用聚合为一的应用。然后各个前端应用还可以独立运行、独立开发、独立部署。

实现微前端的方案

1. 路由转发

我们知道单页面应用的路由控制都是在前端进行,当我们存在多个项目的时候,就算技术栈一样,A项目也指挥不了B里面的路由跳转。但是如果我们将路由跳转交给服务端,当我们访问一个路由的时候,后端进行重定向等操作,这样就会将我们的应用隔离开。
由于不存在跨域,可通过cookie、localstorage等技术进行信息共享。
因为每次路由匹配到的话,都会进行刷新,因此JS,CSS不会互相污染。

缺点:每次跳转都相当于重新刷新了一次页面,不是页面内跳转,体验较差。

优点:配置简单,可快速部署。

2. 嵌套 iframe

通过创建一个父程序,在父程序中监听路由的变化,卸载或加载相应的子程序iframe。因每一个iframe就相当于一个单独的页面,所以iframe具有天然的JS和css隔离。在信息共享方面,可以使用postMessage或者contentWindow的方式进行。

缺点: iframe样式兼容问题。分别为功能性兼容性以及业务性兼容性的问题。并且可能会存在一些安全问题。

  • 主应用劫持快捷键操作
  • 事件无法冒泡到顶层,针对整个应用统一处理时效
  • iframe 内元素会被限制在文档树中,视窗宽高限制问题
  • 无法共享基础库进一步减少包体积
  • 事件通信繁琐且限制多 事件整理参考

优点:实现简单,自带沙盒特性

codesandbox

3. 纯 Web Components 开发

将每个子应用采用 Web Components 进行开发。纯 web-components 相当于自定义了一个html标签,我们就可以在任何的框架中进行使用此标签。例如:

   <template id='userInfo'>
       <div class='user-box'>
       <p class='user-name'>byeL</p>
       <p class='user-sex'>男</p>
        </div>
   </template>
 class UserInfo extends HTMLElement {
     constructor() {
       super();
       var templateElem = document.getElementById('userInfo');
       var content = templateElem.content.cloneNode(true);
       this.appendChild(content);
     }
   } 
   window.customElements.define('user-info', UserCard);Ï
// 直接使用
   <body>
     <link rel="import" href="./UserInfo.js">
   </body>
// 在vue中使用(需要在入口的main中引入userInfo
   <template>
     <user-info></user-info>
   </template>
// 在react中使用(需在入口引入userInfo
   class Hello extends React.Component {
     render() {
       return <div><user-info></user-info></div>;
     }
   }
缺点:之前的子系统都要进行改造,工作量大,并且通信方面较为复杂。原生组件,普及度不高,未来充满不确定性,容易翻车。

优点:每个子应用拥有独立的script和css,也可单独部署。

4. 组合式应用路由

每个子应用单独打包,部署和运行。不过需要基于父应用进行路由管理。例如:有子应用A的路由是/testA,子应用B的路由是/testB,那么父应用在监听到/testA的时候,如果此时处于/testB,那么首先会直接卸载子应用B,在去加载子应用A。

缺点:需要解决样式冲突,JS污染问题,需要有通信技术等。

优点:纯前端改造,相比于路由式,无刷新,体验更好。

5. 模块联邦

基于 Webpack 5 的一种微前端方案,使用其 Module federation 插件。

它的主要功能是可以将项目中的部分组件或全部组件暴露给外部使用,我们也可以引用其他项目中暴露的组件,从而实现模块的复用。这和发布 npm 包然后在其他项目中引用听起来有些相似,本质区别在于,npm的形式如果a包依赖b包,那么b更改发布后需要在a当中install新b包,重新发布a才能应用b的更改,而模块联邦只需要更新b即可。

缺点:依赖webpack,对老项目不友好,不支持js沙盒环境,需要自行实现,第一次需要将引用的依赖前置,可能会导致加载时间变长。

优点:未来可期。

其他方案:EMP微前端方案

方案选择与实践

目前较多的微前端方案采用的技术方案是组合式应用路由分发,那么需要解决的问题是:JS的沙盒环境、css的样式重叠或冲突、以及通信技术问题。

css冲突解决方案:

  1. 类似于vue的scoped。在打包的时候,对css选择器加上响应的属性,属性的key值是一些不重复的hash值,然后在选择的时候,使用属性选择器进行选择。
  2. 可以自定义前缀。在开发子模块之前,需要确定一个全局唯一的css前缀,然后在书写的过程中同一添加此前缀,或在根root上添加此前缀,使用less或sass作用域嵌套即可。例如:
<div class='rootA'>
    <span class='rootA-span'></span>  
</div>
<style>
.root{
  .rootA-span{
    // 子路由A的css
  }
}    
</style>

JS的沙盒环境:

首先我们需要明确的是,如果采用组合式应用路由开发,对于JS上下文有什么影响?我们做个例子:

假如我有个a子应用,会给window上挂在一个函数,函数名是hello,然后我父应用上也有一个函数名是hello,那么在子应用进行加载的时候,就会覆盖父类上的方法。

// 子应用A
window.hello = () => {
  alert('我是子应用A');
};
// 父应用
window.hello = () => {
  alert('我是父应用');
};

基于上面的例子,我们大致就可以看出,沙盒环境最主要做的就是一个js作用域、属性等的隔离。那么在实际的应用中,基本采用以下原理进行隔离:

1. diff方法。

当我们的子页面加载到父类的基座中的时候,我们可以生成一个map的散列表。在页面渲染之前,我们先把当前的window上的变量等都存储在这个map中。当页面卸载的时候,我们在遍历这个map,将其数据在替换回去。

class Sandbox {
  constructor() {
    this.cacheMy = {} // 存放修改的属性
    this.cacheBeforeWindow = {}
  }
  showPage() {
    this.cacheBeforeWindow = {}
    for (const item in window) {
      this.cacheBeforeWindow[item] = window[item]
    }

    Object.keys(this.cacheMy).forEach((p) => {
      window[p] = this.cacheMy[p]
    })
  }

  hidePage() {
    for (const item in window) {
      if (this.cacheBeforeWindow[item] !== window[item]) {
        // 记录变更
        this.cacheMy[item] = window[item]
        // 还原window
        window[item] = this.cacheBeforeWindow[item]
      }
    }
  }
}

const diffSandbox = new Sandbox()
// 模拟页面激活
diffSandbox.showPage() // 激活沙箱
window.info = '我是子应用'
console.log('页面激活,子应用对应的值', window.info)
// 模拟页面卸载
diffSandbox.hidePage()
// 模拟页面激活
console.log('页面卸载后,子应用的对应的值', window.info)
diffSandbox.showPage() // 重新激活
console.log('页面激活,子应用对应的值', window.info)

2. 使用代理

这里需要用到es6的新特性:proxy查看MDN Proxy介绍。原理是监听get和set方法,针对当前路由进行window的属性或方法的存取

const windowMap = new Map();
const resertWindow = {};

let routerUrl = '';
const handler = {
    get: function(obj, prop) {
        const tempWindow = windowMap.get(routerUrl);
        console.log(windowMap, routerUrl);
        return tempWindow[prop];
    },
    set: function(obj, prop, value) {
        if (!windowMap.has(routerUrl)) {
            windowMap.set(routerUrl, JSON.parse(JSON.stringify(resertWindow)));
        }
        const tempWindow =  windowMap.get(routerUrl);
        tempWindow[prop] = value;
        // console.log(obj, prop, value);
    },
};

let proxyWindow = new Proxy(resertWindow, handler);
// 首先是父类的a属性.
proxyWindow.a = '我是父类的a属性的值';
 
// 改变路由到子类
routerUrl = 'routeA';
proxyWindow.a = '我是routerA的a属性的值'

// 改变路由到父类
routerUrl = '';
console.log(proxyWindow.a);

// 改变路由到子类
routerUrl = 'routeA';
console.log(proxyWindow.a);

3. iframe自带css和js沙盒隔离。

single-spa 框架

通过上面的了解,我们知道要实现组合式应用路由开发,首先需要设计一套前端基座,用于调控子项目,single-spa就是其中最广为人知的框架,先以Vue为例看下如何使用它。

1. 在子项目中引入single-spa-vue

npm install single-spa-vue --save

2. 在子类的main.js中加入single-spa-vue相应的生命周期

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import singleSpaVue from 'single-spa-vue'

Vue.config.productionTip = false

const appOptions = {
  el: '#microApp',
  router,
  render: h => h(App)
}

// 支持应用独立运行、部署,不依赖于基座应用
if (!process.env.isMicro) {
  delete appOptions.el
  new Vue(appOptions).$mount('#app')
}
// 基于基座应用,导出生命周期函数
const vueLifecycle = singleSpaVue({
  Vue,
  appOptions
})
// 启动生命周期
export function bootstrap (props) {
  console.log('app2 bootstrap')
  return vueLifecycle.bootstrap(() => {})
}
// 挂载生命周期
export function mount (props) {
  console.log('app2 mount')
  return vueLifecycle.mount(() => {})
}
// 卸载生命周期
export function unmount (props) {
  console.log('app2 unmount')
  return vueLifecycle.unmount(() => {})
}

3. 新建vue.config.js文件,并设置导出格式为umd

const package = require('./package.json')
module.exports = {
  // 告诉子应用在这个地址加载静态资源,否则会去基座应用的域名下加载
  publicPath: '//localhost:8082',
  // 开发服务器
  devServer: {
    port: 8082,
  },
  configureWebpack: {
    // 导出 umd 格式的包,在全局对象上挂载属性 package.name,基座应用需要通过这个全局对象获取一些信息,比如子应用导出的生命周期函数
    output: {
      // library 的值在所有子应用中需要唯一
      library: package.name,
      libraryTarget: 'umd',
    },
  },
}

经过以上几步操作,一个微前端的子项目基本就创建完成了,我们可以看出single-spa在这里只做了一件事,那就是提供生命周期的概念。

4. 创建父类基座。

4.1. 父类基座引入single-spa
npm install single-spa --save
4.2. 改造父类main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerApplication, start } from 'single-spa'

Vue.config.productionTip = false

// 远程加载子应用
function createScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script')
    script.src = url
    script.onload = resolve
    script.onerror = reject
    const firstScript = document.getElementsByTagName('script')[0]
    firstScript.parentNode.insertBefore(script, firstScript)
  })
}

// 记载函数,返回一个 promise
function loadApp(url, globalVar) {
  // 支持远程加载子应用
  return async () => {
    //
    await createScript(url + '/js/chunk-vendors.js')
    await createScript(url + '/js/app.js')
    // 这里的return很重要,需要从这个全局对象中拿到子应用暴露出来的生命周期函数
    return window[globalVar]
  }
}

// 子应用列表
const apps = [
  {
    // 子应用名称
    name: 'app1',
    // 子应用加载函数,是一个promise
    app: loadApp('http://localhost:8083', 'app1'),
    // 当路由满足条件时(返回true),激活(挂载)子应用
    activeWhen: (location) => location.pathname.startsWith('/app1'),
    // 传递给子应用的对象
    customProps: {},
  },
  {
    name: 'app2',
    app: loadApp('http://localhost:8082', 'app2'),
    activeWhen: (location) => location.pathname.startsWith('/app2'),
    customProps: {},
  },
]

// 注册子应用
for (let i = apps.length - 1; i >= 0; i--) {
  registerApplication(apps[i])
}

new Vue({
  router,
  mounted() {
    // 启动
    start()
  },
  render: (h) => h(App),
}).$mount('#app')

经过上面改造完成之后,一个父类基座应用也就创建完毕了,看看它主要做了哪些事:

  1. loadApp主要是根据资源地址去请求资源
  2. createScript主要是将请求回来的script添加到页面中。
  3. apps就是一些子类应用的描述。
  4. registerApplication就是将这些子应用注册到single-spa中。

现在一个由single-spa改造完成的微前端项目就完成了,我们再回头看看其原理流程图

2022/04/1651041217064.png

从中我们可以看出浏览器首次打开父类应用的时候,第一步就是先调用registerApplication注册app,接下来判断当前的路由是属于哪一个子应用的,他的判断依据就是apps中的activeWhen,接下来就会将当前的子应用划分状态,appToLoad,appToUnmounted, appToMounted。接下来根据子应用的状态,先去执行需要卸载的子应用,卸载完成之后,就会去执行状态为appToLoad, appToMounted的子应用,那么在最后在执行相应的回调函数,也就是我们在子应用中注册的那些生命周期。

前面铺垫了这么多,我们发现single-spa就只做了两件事:提供生命周期概念与负责调度子应用的生命周期,对于组合式应用路由模式还有其它尚未解决的问题怎么办呢,这时候就要讲下一个框架:qiankun

qiankun 框架

qiankun 是一个基于 single-spa 的微前端实现库,样式隔离、JS沙盒环境、空闲预加载这些qiankun都帮我们做了,不需要自己去处理。在single-spa的开发过程中,我们需要自己手动的去写调用子应用JS的方法(如上面的 createScript方法),而qiankun不需要,乾坤只需要你传入响应的apps的配置即可,会帮助我们去加载。

说到资源加载方案,可分为两种:

1. JS Entry

该方式通常是子应用将资源打成一份 script,这个方案的限制也颇多,如要求子应用的所有资源打包到一个 js bundle 里,包括 css、图片等资源。除了打出来的包可能体积庞大之外的问题之外,资源的并行加载等特性也无法利用上。

2. HTML Entry (qiankun所采用的)

这种方式更加灵活,直接将子应用打出来 HTML 作为入口,主框架可以通过 fetch html 的方式获取子应用的静态资源,同时将 HTML document 作为子节点塞到主框架的容器中。这样不仅可以极大的减少主应用的接入成本,子应用的开发方式及打包方式基本上也不需要调整,而且可以天然的解决子应用之间样式隔离的问题(后面提到)。

qiankun微前端实践

项目架构简述:1. 基座采用:vue,2. 子项目:vue 2/3 、 react

说明:vue 通过 vue cli 进行创建, react 通过 create-react-app 进行项目创建。

完整示例 Demo -> Micro-Frontends-Practice,以下篇幅为过程实现,代码片段经过反复修改,有可能存在部分错误,具体实现参考demo。

在创建完父类项目以及子类项目之后,安装好 qiankun 依赖库。

npm install qiankun --save

在安装完成之后,我们需要对其做相应的处理。分为三部分:

1. 在父类中创建微前端基座

首先在 src 下创建一个子应用的配置文件,我们叫做 micro_app.js

// src/micro_app.js

const microApps = [
  {
    name: 'micro_vue',
    entry: '//localhost:8081/',
    activeRule: '/micro_vue',
    container: '#subapp-viewport', // 子应用挂载的div
    props: {
      routerBase: '/micro_vue', // 下发路由给子应用,子应用根据该值去定义qiankun环境下的路由
    },
  },
  {
    name: 'micro_react',
    entry: '//localhost:10100/',
    activeRule: '/micro_react',
    container: '#subapp-viewport', // 子应用挂载的div
    props: {
      routerBase: '/micro_react',
    },
  },
]

export default microApps

在设置完子应用的配置文件之后,我们需要改写 App.vue

// 为了简单,只是改写了模板文件,
<template>
  <div id="layout-wrapper">
    <div class="layout-header">头部导航</div>
    <div id="subapp-viewport"></div>
  </div>
</template>   

接下来是更改 main.js。在 main.js 中最主要做的事情就是将之前配置的子应用进行注册并且启动 qiankun 框架服务

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import { registerMicroApps, start } from 'qiankun'
import appConfig from './micro_app'
Vue.config.productionTip = false

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

// 注册子应用
registerMicroApps(appConfig, {
  beforeLoad: (app) => {
    console.log('before load app.name====>>>>>', app.name)
  },
  beforeMount: [
    (app) => {
      console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
    },
  ],
  afterMount: [
    (app) => {
      console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
    },
  ],
  afterUnmount: [
    (app) => {
      console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
    },
  ],
})
// 启动qiankun
start()   

父类的基座创建完成之后,接下来就是子应用的注册了。

2. vue 接入 qiankun

首先需要在项目根目录下建立 vue.config.js 用来改写 webpack 的相应配置。在 src 下创建 public-path.js。public-path 用来改写 webpack 打包好之后,通过那个路径可以访问到打包后的文件。

// vue.config.js
const { name } = require('./package.json')

module.exports = {
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
  devServer: {
    port: 8081, // 在.env中VUE_APP_PORT=7788,与父应用的配置一致
    headers: {
      'Access-Control-Allow-Origin': '*', // 主应用获取子应用时跨域响应头
    },
  },
}
// public-path.js

;(function () {
  // 用来判断是否运行在乾坤的框架下
  if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
  }
})()

修改 router 下的 Index,我们只需要导出对应的路径定义即可,不需要导出对应的 vue-router 实例,如果直接导出 vue-router 实例的话,会与我们在父类 micro_app.js 中设置的 routerBase 匹配不上。导致渲染子应用失败。

import Home from '../views/Home.vue'
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
]
export default routes

接下来就是需要改写 main.js。同样是生命周期的引入。并且注册 vue-vouter 实例。

import './public-path' // 注意需要引入public-path
import Vue from 'vue'
import App from './App.vue'
import routes from './router'
import store from './store'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue.config.productionTip = false
let instance = null
window.projectName = 'micro_vue'
function render(props = {}) {
  // 这个是我们在父类注册的时候定义的那些参数。
  const { container, routerBase } = props
  const router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL,
    mode: 'history',
    routes,
  })
  instance = new Vue({
    router,
    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
}

最后需要在根目录下创建.env 文件。

VUE_APP_PORT = 8081

以上步骤完成之后,我们只需要启动项目我们既可以通过对应的路由进行访问了http://localhost:xxxx/micro_vue

完整示例 Demo -> Micro-Frontends-Practice

3. react 项目的接入

在 src 下创建 public-path.js 文件

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

接下来修改index.js

首先我们需要引入react-router-dom

// npm install react-router-dom --save
import { BrowserRouter as Router } from 'react-router-dom'
// 接下来我们需要对render进行改写
function render(props) {
  const { container } = props
  ReactDOM.render(
    <Router basename={window.__POWERED_BY_QIANKUN__ ? '/micro_react' : '/'}>
      <App />
    </Router>,
    container ? container.querySelector('#root') : document.querySelector('#root'),
  )
}
// 然后加入生命周期即可
export async function bootstrap() {
  console.log('ReactMicroApp bootstraped')
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log('ReactMicroApp mount', props)
  render(props)
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log('ReactMicroApp unmount')
  ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
修改 webpack 配置
方式 1:

运行 npm run eject

修改config/webpackDevServer.config.js

module.exports = function (proxy, allowedHost) {
  return {
    headers: {
      'Access-Control-Allow-Origin': '*', // 表示允许跨域
    },
    ...
}

接下来修改config/webpack.config.js

return {
  ...
  output: {
    // The build folder.
    ...
    // 微应用配置
    library: `${appPackageJson.name}-[name]`,
    libraryTarget: 'umd'
  },
  ...
}
方式2:

安装插件 @rescripts/cli,当然也可以选择其他的插件,例如 react-app-rewired

npm i -D @rescripts/cli

根目录新增 .rescriptsrc.js

const { name } = require('./package')

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`
    config.output.libraryTarget = 'umd'
    config.output.jsonpFunction = `webpackJsonp_${name}`
    config.output.globalObject = 'window'

    return config
  },

  devServer: (_) => {
    const config = _

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    }
    config.historyApiFallback = true
    config.hot = false
    config.watchContentBase = false
    config.liveReload = false

    return config
  },
}

修改 package.json

         -   "start": "react-scripts start",
         +   "start": "rescripts start",
         -   "build": "react-scripts build",
         +   "build": "rescripts build",
         -   "test": "react-scripts test",
         +   "test": "rescripts test",

最后,同理在根目录下创建一个.env 文件,

PORT = 10100
BROWSER = none

然后启动 react 项目,之后就可以通过http://localhost:xxx/micro_react进行访问了

完整示例 Demo -> Micro-Frontends-Practice

数据交互

qiankun 内部使用 initGlobalState(state)定义全局状态,该方法执行后返回一个 MicroAppStateActions 实例,实例中包含三个方法,分别是 onGlobalStateChange、setGlobalState、offGlobalStateChange。

默认会通过 props 将通信方法传递给子应用。

  • onGlobalStateChange: 当 global 有数据发生更改的时候,就会触发回调函数,回调函数传入的是更改后的 globalState
  • setGlobalState: 更改 globalstate 的方法
  • offGlobalStateChange: 移除监听,微应用执行 umount 时会默认调用。

如何使用:

首先我们在父类基座的 src 下建一个文件为action.js文件。

import { initGlobalState } from 'qiankun'
import store from './store'

const initialState = {
  //这里写初始化数据
  userName: 'testInfo',
  userId: '1232',
}

// 初始化 state
const actions = initGlobalState(initialState)
actions.onGlobalStateChange((state, prev) => {
  //监听公共状态的变化
  console.log('主应用: 变更前')
  console.log(prev)
  console.log('主应用: 变更后')
  console.log(state)
})
export default actions

在父类如何获取初始的GlobalState:

// 当onGlobalStateChange的最后一个属性为true的时候,表示立即获取,不需要等待数据发生变更
action.onGlobalStateChange((state) => {
  console.log('全局状态发生改变')
  // this.mes = state
  this.userInfo.userName = state.userName
  console.log(state)
}, true)

在父类中更改GlobalState:

action.setGlobalState({
  userName: '更改之后的名称',
  userId: 'new Id',
})

如何在子类中进行使用:

首先在子类中 src 下创建一个 action.js

function emptyAction() {
  //设置一个actions实例
  // 提示当前使用的是空 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 中接受 props 上传递过来的方法

export async function mount(props) {
  console.log('[vue] props from main framework', props)
  actions.setActions(props)
  render(props)
}

之后的获取数据,以及更新与父类的相同。

结尾

总结我对微前端的思考,我认为它或许会成为主流,但未来不一定完全属于微前端。因为我们希望的一直都是——可以使用熟悉的技术、或是新潮的技术去完成开发工作,微前端既是在这种情况下诞生的。而解决大型系统各方各面的问题,可以是统一治理,也可以是分而治之,微前端则是后者。
所以我们当下既要全面了解各种微前端的技术,也不用急于去追随潮流,强行微前端化。如果争取在实践中获得更多经验与思考,不论什么技术都会有派上用场的时候。

相关文章
|
2月前
|
人工智能 前端开发 JavaScript
前端架构思考 :专注于多框架的并存可能并不是唯一的方向 — 探讨大模型时代前端的分层式微前端架构
随着前端技术的发展,微前端架构成为应对复杂大型应用的流行方案,允许多个团队使用不同技术栈并将其模块化集成。然而,这种设计在高交互性需求的应用中存在局限,如音视频处理、AI集成等。本文探讨了传统微前端架构的不足,并提出了一种新的分层式微前端架构,通过展示层与业务层的分离及基于功能的横向拆分,以更好地适应现代前端需求。
|
23天前
|
移动开发 缓存 前端开发
深入理解前端路由:原理、实现与应用
本书《深入理解前端路由:原理、实现与应用》全面解析了前端路由的核心概念、工作原理及其实现方法,结合实际案例探讨了其在现代Web应用中的广泛应用,适合前端开发者和相关技术人员阅读。
|
1月前
|
前端开发 开发者
本文将深入探讨 BEM 的概念、原理以及其在前端开发中的应用
BEM(Block-Element-Modifier)是一种前端开发中的命名规范和架构方法,旨在提高代码的可维护性和复用性。通过将界面拆分为独立的模块,BEM 提供了一套清晰的命名规则,增强了代码的结构化和模块化设计,促进了团队协作。本文深入探讨了 BEM 的概念、原理及其在前端开发中的应用,分析了其优势与局限性,为开发者提供了宝贵的参考。
52 8
|
1月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
73 1
|
1月前
|
监控 前端开发 jenkins
Jenkins 在前端项目持续部署中的应用,包括其原理、流程以及具体的实现方法
本文深入探讨了Jenkins在前端项目持续部署中的应用,涵盖其基本原理、流程及具体实现方法。首先介绍了Jenkins的基本概念及其在自动化任务中的作用,随后详细解析了从前端代码提交到生产环境部署的全过程,包括构建、测试、部署等关键步骤。最后,强调了持续部署中的代码质量控制、环境一致性、监控预警及安全管理等注意事项,旨在帮助开发者高效、安全地实施持续部署。
63 5
|
1月前
|
前端开发 API UED
深入理解微前端架构:构建灵活、高效的前端应用
【10月更文挑战第23天】微前端架构是一种将前端应用分解为多个小型、独立、可复用的服务的方法。每个服务独立开发和部署,但共同提供一致的用户体验。本文探讨了微前端架构的核心概念、优势及实施方法,包括定义服务边界、建立通信机制、共享UI组件库和版本控制等。通过实际案例和职业心得,帮助读者更好地理解和应用微前端架构。
|
2月前
|
前端开发 API UED
拥抱微前端架构:构建灵活、高效的前端应用
【10月更文挑战第17天】微前端架构是一种将前端应用拆分为多个小型、独立、可复用的服务的方法,每个服务可以独立开发、部署和维护。本文介绍了微前端架构的核心概念、优势及实施步骤,并分享了业界应用案例和职业心得,帮助读者理解和应用这一新兴架构模式。
|
2月前
|
存储 监控 前端开发
掌握微前端架构:构建未来前端应用的基石
【10月更文挑战第12天】随着前端技术的发展,传统的单体应用架构已无法满足现代应用的需求。微前端架构通过将大型应用拆分为独立的小模块,提供了更高的灵活性、可维护性和快速迭代能力。本文介绍了微前端架构的概念、核心优势及实施步骤,并探讨了其在复杂应用中的应用及实战技巧。
|
2月前
|
缓存 JavaScript 前端开发
拿下奇怪的前端报错(三):npm install卡住了一个钟- 从原理搞定安装的全链路问题
本文详细分析了 `npm install` 过程中可能出现的卡顿问题及解决方法,包括网络问题、Node.js 版本不兼容、缓存问题、权限问题、包冲突、过时的 npm 版本、系统资源不足和脚本问题等,并提供了相应的解决策略。同时,还介绍了开启全部日志、使用替代工具和使用 Docker 提供 Node 环境等其他处理方法。
1303 0
|
2月前
|
存储 安全 前端开发
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
在前端开发中需要考虑的常见web安全问题和攻击原理以及防范措施
230 0