微前端项目难点解决(二)

简介: 微前端项目难点解决

多个子应用并行加载,子应用嵌套


  1. 同一个基座并行加载两个或者多个子应用

可以使用loadMicroApp加载多个子应用

      2. 多路由系统共存带来的 冲突/抢占 问题如何解决?

let historyPath=window.location.pathname.startWith('/vue1/')?process.env.BASE_URL+'/vue1/':process.env.BASE_URL
const router = createRouter({
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? historyPath : process.env.BASE_URL),
    // history: createWebHashHistory(),
    routes: constantRoutes,
})

同时调用startloadMicroApp会导致子应用render两次,虽然对页面结构和样式没有影响,但是接口都会调用两次,所以在跳出子应用的时候一定要loadMicroApp.unmount()卸载不需要的子应用


qiankun代码入口函数封装


import type { MicroAppStateActions } from 'qiankun';
import QiankunBridge from '@/qiankun/qiankun-bridge'
import {
    initGlobalState,
    registerMicroApps,
    start,
    loadMicroApp,
    addGlobalUncaughtErrorHandler,
    runAfterFirstMounted
} from 'qiankun';
import { getAppStatus, unloadApplication } from 'single-spa';
export default class Qiankun {
    private actions: MicroAppStateActions | null = null
    private appMap: any = {}
    private prefetchAppMap: any = {}
    init() {
        this.registerApps()
        this.initialState()
        this.prefetchApp();
        this.errorHandler();
    }
    registerApps(){
        const parentRoute = useRouter();
        registerMicroApps([{
            name: 'demo-vue',
            entry: `${publicPath}/demo-vue`,
            container: `#demo-vue`,
            activeRule: `${publicPath}${'demo-vue'}`,
            props: {
                parentRoute,
                QiankunBridge:new QiankunBridge()
            }
        }, ...]);
    }
    initialState(){  
        const initialState = {};
        // 初始化 state
        this.actions = initGlobalState(initialState);
        this.actions.onGlobalStateChange((state, prev) => {
            // state: 变更后的状态; prev 变更前的状态
            console.log(state, prev);
        });
    }
    setState(state: any) {
        this.actions?.setGlobalState(state);
    }
    //预加载
    prefetchApp() {
        start({
            prefetch: 'all',
            singular: false,
        });
    }
    //按需加载
    demandLoading(apps){
       let installAppMap = {
          ...store.getters["tabs/installAppMap"],
       };
       if (!installAppMap[config.name]) {
          installAppMap[config.name] = loadMicroApp({
            ...config,
            configuration: {
                // singular: true
                sandbox: { experimentalStyleIsolation: true },
            },
            props: {
                getGlobalState: actions.getGlobalState,
                fn: {
                    parentRoute: useRouter(),
                    qiankunBridge: qiankunBridge,
                },
            },
        });
       }
    }
    /**
     * @description: 卸载app
     * @param {Object} app 卸载微应用name, entry
     * @returns false
     */
    async unloadApp(app) {
        // await clearCatchByUrl(getPrefetchAppList(addVisitedRoute, router)[0])
        const appStatus = getAppStatus('utcus');
        if (appStatus !== 'NOT_LOADED') {
            unloadApplication(app.name);
            // 调用unloadApplication时,Single-spa将执行以下步骤。
            // 在要卸载的注册应用程序上调用卸载生命周期。
            // 将应用程序状态设置为NOT_LOADED
            // 触发重新路由,在此期间,单spa可能会挂载刚刚卸载的应用程序。
            // 由于unloadApplication调用时可能会挂载已注册的应用程序,因此您可以指定是要立即卸载还是要等待直到不再挂载该应用程序。这是通过该waitForUnmount选项完成的。
        }
    }
    //重新加载微应用
    reloadApp(app) {
        this.unloadApp(app).then(() => {
            loadMicroApp(app);
        });
    }
    //加载单个app
    loadSingleApp(name) {
        if (!this.appMap[name]) {
            this.appMap[name] = loadMicroApp(this.prefetchAppMap[name]);
        }
    }
    // 切出单个app,和unloadApp用法不同unloadApp 是卸载start方法生成的应用,unmountSingleApp是卸载loadMicroApp方法生成的应用
    async unmountSingleApp(name) {
        if (this.appMap[name]) {
            await this.appMap[name].unmount();
            this.appMap[name] = null;
        }
    }
    //错误处理
    errorHandler() {
        addGlobalUncaughtErrorHandler((event: any) => {
            console.log('addGlobalUncaughtErrorHandler', event);
            if (
                event?.message &&
                event?.message.includes('died in status LOADING_SOURCE_CODE')
            ) {
                Message('子应用加载失败,请检查应用是否运行', 'error', false);
            }
            //子应用发版更新后,原有的js会找不到,所以会报错
            if (event?.message && event?.message.includes("Unexpected token '<'")) {
                Message('检测到项目更新,请刷新页面', 'error', false);
            }
        });
    }
}


应用事件通信


应用场景:子应用a调用子应用b的事件

const isDuplicate = function isDuplicate(keys: string[], key: string) {
    return keys.includes(key);
};
export default class QiankunBridge {
    private handlerMap: any = {}
    // 单例判断
    static hasInstance = () => !!(window as any).$qiankunBridge
    constructor() {
        if (!QiankunBridge.hasInstance()) {
            ; (window as any).$qiankunBridge = this;
        } else {
            return (window as any).$qiankunBridge;
        }
    }
    //注册
    registerHandlers(handlers: any) {
        const registeredHandlerKeys = Object.keys(this.handlerMap);
        Object.keys(handlers).forEach((key) => {
            const handler = handlers[key];
            if (isDuplicate(registeredHandlerKeys, key)) {
                console.warn(`注册失败,事件 '${key}' 注册已注册`);
            } else {
                this.handlerMap = {
                    ...this.handlerMap,
                    [key]: {
                        key,
                        handler,
                    },
                };
                console.log(`事件 '${key}' 注册成功`);
            }
        });
        return true;
    }
    removeHandlers(handlerKeys: string[]) {
        handlerKeys.forEach((key) => {
            delete this.handlerMap[key];
        });
        return true;
    }
    // 获取某个事件
    getHandler(key: string) {
        const target = this.handlerMap[key];
        const errMsg = `事件 '${key}' 没注册过`;
        if (!target) {
            console.error(errMsg);
        }
        return (
            (target && target.handler) ||
            (() => {
                console.error(errMsg);
            })
        );
    }
}

子应用a注册

import React from "react";
export async function mount(props) {
    if (!instance) {
        React.$qiankunBridge = props.qiankunBridge;
        render(props);
    }
}
React.$qiankunBridge &&
React.$qiankunBridge.registerHandlers({
    event1: event1Fn
});

子应用b调用

Vue.$qiankunBridge.getHandler('event1')


项目部署,提示用户更新系统


插件在注册 serviceWorker 时判断 registrationwaiting 状态, 从而判定 serviceWorker 是否存在新版本, 再执行对应的更新操作, 也就是弹窗提示; 有个弊端就是项目有可能需要经常发版改一些bug,导致更新弹窗频繁出现,所以只能弃用。

对于主项目简单一点的方案就是把版本号数据写入cookie里,通过webpack生成一个json文件部署到服务器,前端代码请求到json文件的版本号做对比,如果不是最新版就弹窗系统更新弹窗。

子项目更新的话,js,css资源会重新编译生成新的链接,所以请求不到, addGlobalUncaughtErrorHandler监听到资源请求错误,直接提示更新弹窗就好

addGlobalUncaughtErrorHandler((event: any) => {
    //子应用发版更新后,原有的文件会找不到,所以会报错
    if (event?.message && event?.message.includes("Unexpected token '<'")) {
        Message('检测到项目更新,请刷新页面', 'error', false);
    }
});


主应用组件重新渲染导致子应用dom消失


问题出现场景:ipad移动端切到宽屏,布局发生变价,vue组件重新渲染导致微服务里面的dom消失 。

解决方案:用single-spaunloadApplication方法卸载子应用,用qiankunloadMicroApp方法重新加载该子应用。

详见上文的 reloadApp方法


切换应用后原有子应用路由变化监听失效


function render(props = {}) {
  const { container } = props
  router=Router() //添加此行代码,react子应用清空下dom重新调用下ReactDOM.render即可
  instance = createApp(App)
  instance
    .use(router)
    .mount(
      container
        ? container.querySelector('#app-vue')
        : '#app-vue'
    )
}

目录
相关文章
|
8月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
461 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
10月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
368 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
9月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
626 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
9月前
|
安全 前端开发 开发工具
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
472 5
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
|
9月前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
498 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
10月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
346 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
9月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
299 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
8月前
|
人工智能 JavaScript 前端开发
Vue 性能革命:揭秘前端优化的终极技巧;Vue优化技巧,解决Vue项目卡顿问题
Vue在处理少量数据和有限dom的情况下技术已经非常成熟了,但现在随着AI时代的到来,海量数据场景会越来越多,Vue优化技巧也是必备技能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
488 12
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
10月前
|
缓存 前端开发 IDE
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
274 0
【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
  • 2
    前端工程化演进之路:从手工作坊到AI驱动的智能化开发
  • 3
    Vue 3 + TypeScript 现代前端开发最佳实践(2025版指南)
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
  • 下一篇
    oss云网关配置