基础概念
-
概念:一种构架风格,多个复杂、独立的前端应用并放到同一个运行环境中组成一个更大整体。
-
诞生理由/解决的问题
- MPA:不同团队之间数据,逻辑难以共享
- SPA: 如果整个都是一个SPA应用,数百个页面的时候会遇到 webpack构建会花费数十分钟,难以提升 有瓶颈限制
-
主要特点
a. 低耦合 容易拆分 (子应用之间数据共享,逻辑复用,并保持团队的独立性)
b. 不限技术栈 -
效果实现:
(1)single-SPA,qiankun,icestark运行时方案,切换路由时去请求子应用的资源再去渲染
(2)EMP构建时方案-资源加载提前
(3) iframe (隔离性无法被突破,导致应用间上下文无法被共享,所以微前端基本都放弃使用了iframe)qiankun 、 icestark 都是基于single-SPA的封装
微前端框架 - qiankun使用
A. 配置
一、主应用中 注册子应用的配置
registerMicroApps
// main.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', //子应用名称
entry: '//localhost:7100', // 项目资源的地址 字符串格式
container: '#yourContainer', // 放置的容器
activeRule: '/yourActiveRule', // 路由 匹配到就渲染这个子应用
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] }, // 对象格式
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
loadMicroApp动态加载子应用的时候
使用场景:子应用嵌套在一个动态打开的窗口时 或者 通过异步来控制是否渲染这个子应用时
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer',
});
二、子应用改造
- 导出子应用的生命周期钩子(供qiankun和子应用去做通信)
- 怎么做:
// 子应用的main.js中
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会重复触发
// 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
export async function bootstrap() {
console.log('vue app bootstraped');
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
// vue
console.log('[vue] props from main framework', props)
render(props)
}
// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount(props) {
// vue
console.log('unmount', props)
app.$destory()
}
// 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
export async function update(props) {
console.log('update props', props);
}
-
为什么:
比如路由切换来去做子应用的渲染前,当前子是空白的 这时候可能需要做一些loading动画,这个场景怎么感应到。就需要配置那三个钩子,以便父子生命周期上的一些交互感应【监听到父应用当前做了哪些事情,然后在对应时机去实现什么事】 -
生命周期钩子:
bootstrap:初始化只调用一次,整个全局生命周期中也只会调用一次【只会调用一次!初始化调用一次后就销毁了,注意区分mount】mount:每次都会调用(切换的时候每次都会触发)unmount:切换的时候卸载子应用
- 配置打包工具【重要步骤: 为了让父识别到子应用暴露的信息】
// vue.config.js
const packageName = require('./package.json').name;
module.exports={
configureWebpack: {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${packageName}`
}
}
}
切换应用路由变化了,子应用没有渲染,浏览器也没报错,大概率是因为打包的配置有问题,导致父无法感知到子的声明周期入口钩子,无法侵入触发执行逻辑去正常render
B. 使用
通信实例三个方法:
setGlobalState(传递的数据)onGlobalStateChange(cb)监听offGlobalStateChange()移除监听
- 主应用:
initGlobalState(state)返回通信方法actionssetGlobalState(传递的数据)
- 微应用:微应用生命周期
mount中通过props获取通信方法,方法api和上面一致onGlobalStateChange(cb)监听获取数据
export function mount(props) { props.onGlobalStateChange((state, prev) => { // state: 传递来的参数 console.log(state, prev) }) }
C. 工作原理
切换渲染流程:
graph TD
匹配到路径 --> 配置中找到enrty发送网络请求 --> 服务器返回对应的HTML资源 --> 微前端拿到模板后进入到渲染流程
-
通过劫持监听路由改变,根据activeRule去匹配,匹配到了去执行加载微应用
-
渲染
a. 根据entry写的域名,通过如fetch发起真实请求,拿到页面返回的HTML页面
b. 通过如下方式来解析拿到的页面qiankun的解析是通过 single-SPA 提供的api importHTMLentry来实现的。 动态加载html资源并做解析,把加载下来的子应用的html文件按照标签做拆分并做归类。 比如script归到script,样式表归到样式表,然后进入对应的处理逻辑。 对于样式表采用动态插入style的方式 ,js可能用new funciton或者eval来执行js片段。 执行后解析完了进入子页面的渲染逻辑中c. 进入子应用的生命周期逻辑执行
bootstrap ---> mount (mount 里注册了子应用的render方法,new Vue 去挂载 ) --> 微应用页面渲染完成路由切换的时候:之前的微应用1 umount,并加载微应用2的一套