什么是微前端,应用场景,优点

理解:我们可以简单理解为微前端是将一个项目拆分成多个模块,每个微前端模块可以由不同的团队进行管理,并可以自主选择框架,并且有自己的仓库,可以独立部署上线 应用场景:1.当公司代码较老需要使用新的技术栈时我们可以使用微前端。2.多个团队同时开发时,每个团队单独维护模块。3.新增业务模块时,直接创建一个新的项目根据团队情况选择技术栈 优点:团队自治,兼容老项目,独立开发/部署,技术灵活

qiankun框架

了解iframe

iframe是最早可以实现主应用嵌套子引用的标签,每个子应用通过iframe标签来嵌入到父应用中,iframe具有天然的隔离属性,各个子应用之间以及子应用和父应用之间都可以做到互不影响。 缺点: 1.url不同步,如果刷新页面,iframe中的页面的路由会丢失。 2.全局上下文完全隔离,内存变量不共享。(父应用和子应用window对象不一致) 3.UI不同步,在子应用使用ui组件时只在子应用范围内生效,不会占用父应用空间。 4.慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

了解single-spa

single-spa是最早的微前端框架,可以兼容很多技术栈。解决了iframeURL不同步和UI不同步的情况 缺点: 1.没有实现js隔离和css隔离 2.需要修改大量的配置,包括基座和子应用的,不能开箱即用

qiankun

了解

qiankun是阿里开源的一个微前端的框架,在阿里内部已经经过一批线上应用的充分检验及打磨了,所以可以放心使用

优势:

1.基于single-spa封装的,提供了更加开箱即用的API

2.技术栈无关,任意技术栈的应用均可使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。

3.HTML Entry的方式接入,像使用iframe一样简单

4.实现了single-spa不具备的样式隔离和js隔离

5.资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

实际应用

前情提要

1.项目基于react18版本进行构建,其余子应用均采用最新版本 2.react16版本推荐学习地址视频连接

项目目录

1.基座(主应用):主要负责集成所有的子应用,提供一个入口能够访问你所需要的子应用的展示,尽量不写复杂的业务逻辑 2.子应用:根据不同业务划分的模块,每个子应用都打包成umd模块的形式供基座(主应用)来加载 3.基座采用react18

基座改造(主应用)

1.安装qiankun

npm i qiankun // 或者 yarn add qiankun

2.修改入口文件

// 在src/index.tsx中增加如下代码

import { start, registerMicroApps } from 'qiankun';

// 1. 要加载的子应用列表

const apps = [

{

name: "sub-react", // 子应用的名称

entry: '//localhost:8080', // 默认会加载这个路径下的html,解析里面的js

activeRule: "/sub-react", // 匹配的路由

container: "#sub-app" // 加载的容器

},

]

// 2. 注册子应用,qiankun会根据activeRule去匹配对应的子应用并加载

registerMicroApps(apps, {

beforeLoad: [async app => console.log('before load', app.name)],

beforeMount: [async app => console.log('before mount', app.name)],

afterMount: [async app => console.log('after mount', app.name)],

})

start() // 3. 启动微服务 启动 qiankun,可以进行预加载和沙箱设置

react子应用

使用create-react-app脚手架创建,webpack进行配置,为了不eject所有的webpack配置,我们选择用react-app-rewired工具来改造webpack配置。 1.下载react-app-rewired

npm i react-app-rewired// 或者 yarn add react-app-rewired

2.在 src 目录新增 public-path.js

if (window.__POWERED_BY_QIANKUN__) {

// 动态设置 webpack publicPath,防止资源加载出错

// eslint-disable-next-line no-undef

__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__

}

3.改造子应用的入口文件

import React from 'react';

import { createRoot } from 'react-dom/client';

import './index.css';

import App from './App';

import { BrowserRouter } from 'react-router-dom'

import './public-path.js'

let root: any;

function render(props: any) {

const { container } = props

//判断当前启动环境是否为qiankun环境下

const dom = container ? container.querySelector('#root') : document.getElementById('root')

root = createRoot(dom)

root.render(

)

}

// 判断是否在qiankun环境下,非qiankun环境下独立运行

if (!(window as any).__POWERED_BY_QIANKUN__) {

render({});

}

// 各个生命周期

// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。

export async function bootstrap() {

console.log('react app bootstraped');

}

// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法

export async function mount(props: any) {

console.log(props)

props.onGlobalStateChange((state, prev) => {

// state: 变更后的状态; prev 变更前的状态

console.log(state, prev);

// 将这个state存储到我们子应用store

});

props.setGlobalState({ count: 2 });

render(props);

}

// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例

export async function unmount(props: any) {

root.unmount();

}

4.修改package.json

"scripts": {

"start": "react-app-rewired start",

"build": "react-app-rewired build",

"test": "react-app-rewired test",

"eject": "react-app-rewired eject"

},

5.配置启动端口号

在根目录下新增.env文件

.env文件下写入PORT=3001

6.修改webpack配置文件

// 在根目录下新增config-overrides.js文件并新增如下配置

const { name } = require("./package");

module.exports = {

webpack: (config) => {

config.output.library = `${name}-[name]`;

config.output.libraryTarget = "umd";

config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;

return config;

}

};

vue子应用

1.创建子应用,选择vue3+vite

npm create vite@latest

2.安装vite-plugin-qiankun依赖包

npm i vite-plugin-qiankun // yarn add vite-plugin-qiankun

3.修改vite.config.ts

import { defineConfig } from 'vite'

import vue from '@vitejs/plugin-vue'

import qiankun from 'vite-plugin-qiankun';

export default defineConfig({

base: '/sub-vue', // 和基座中配置的activeRule一致

server: {

port: 3002,

cors: true,

origin: 'http://localhost:3002'

},

plugins: [

vue(),

qiankun('sub-vue', { // 配置qiankun插件

useDevMode: true

})

]

})

4.修改main.ts

import { createApp } from 'vue'

import './style.css'

import App from './App.vue'

import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

import router from './router'

let app: any;

//判断是否为乾坤环境

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {

createApp(App).use(router).mount('#app');

} else {

renderWithQiankun({

mount(props) {

app = createApp(App);

app.use(router).mount(props.container.querySelector('#app'));

},

bootstrap() {

console.log('vue app bootstrap');

},

update() {

console.log('vue app update');

},

unmount() {

console.log('vue app unmount');

app?.unmount();

}

});

}

umi子应用

1.安装

npm i @umijs/plugins

2.配置.umirc.ts

export default {

base: '/sub-umi',

npmClient: 'npm',

plugins: ['@umijs/plugins/dist/qiankun'],

qiankun: {

slave: {},

}

};

完成上面两步就可以在基座中看到umi子应用的加载了。 3.配置生命周期

src下创建app.ts文件

export const qiankun = {

async mount(props: any) {

console.log(props)

},

async bootstrap() {

console.log('umi app bootstraped');

},

async afterMount(props: any) {

console.log('umi app afterMount', props);

},

};

补充

样式隔离

qiankun实现了各个子应用之间的样式隔离,但是基座和子应用之间的样式隔离没有实现,所以基座和子应用之前的样式还会有冲突和覆盖的情况。 解决方案:每个应用的样式使用固定的格式,通过css-module的方式给每个应用自动加上前缀

子应用的跳转

1.主应用和微应用都是 hash 模式,主应用根据 hash 来判断微应用,则不用考虑这个问题。

2.history模式下微应用之间的跳转,或者微应用跳主应用页面,直接使用微应用的路由实例是不行的,

原因是微应用的路由实例跳转都基于路由的 base。有两种办法可以跳转:

i.history.pushState()

ii.将主应用的路由实例通过 props 传给微应用,微应用这个路由实例跳转。

使用history.pushState()实现跳转: window.history.pushState({}, '', '/sub-vue')

全局状态管理

// 基座初始化

import { initGlobalState } from 'qiankun';

const actions = initGlobalState(state);

// 主项目项目监听和修改

actions.onGlobalStateChange((state, prev) => {

// state: 变更后的状态; prev 变更前的状态

console.log(state, prev);

});

actions.setGlobalState(state);

// 子项目监听和修改

export function mount(props) {

props.onGlobalStateChange((state, prev) => {

// state: 变更后的状态; prev 变更前的状态

console.log(state, prev);

});

props.setGlobalState(state);

}

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: