浅聊一下最近使用微应用micro-app踩到的坑

前言

问题产生来源:项目整体微前端的框架,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。项目初期每个子应用的菜单均在本应用下,所以面包屑可以正常展示。中期客户突然提出想按照功能去划分菜单,导致菜单位置发生大面积变动。一开始感觉没什么,配权限的时候,按照相应的菜单位置去配置就好啦,前端没啥工作量啊。(想太轻易了....)后续发现的问题就是原本属于A应用下的B菜单,现在归属到了C应用。所以原本面包屑是A/B,现在应该得是C/B。而又因为面包屑的取值是在路由文件中,层级是子应用中的路由层级。所以展示出的还是A/B。

解决思路

举例为在A应用中的B菜单在C应用的解决方案,A应用中的B菜单在A应用同理
  1. 按照目前的应用菜单位置,梳理出一份面包屑配置文件-json格式,该文件需要放到基座应用中。
js 复制代码
        "id": "APP_C",    //C应用的唯一标识
        "name": "C应用",   //C应用的名称
        "children": [
            {
                "id": "APP_C_B",  //C应用下的B菜单的唯一标识
                "name": "B菜单",   //C应用下的B菜单的名称
                "url": "xxxxxxxx"  //C应用下的B菜单的url地址
            }
        ]
    } 

2.在A应用的路由文件中meta对象中id,此id应与配置文件中的id相同

js 复制代码
const routeInfo = [
  {
    path: '*******',
    meta: {
          id: 'APP_C_B'   //B菜单对应的id
        },
        //若还有下级可继续编码,面包屑配置文件同理
    children: [
      {
        path: '*******',
        meta: {
          id: ''
        },
        children: []
      }
    ]
  }
]
export default routeInfo

3.在A应用的路由文件中添加全局前置守卫,通过该守卫A应用向基座应用发送数据。

js 复制代码
const useRouterCreate = () => {
  const router = createRouter({
    history: createWebHashHistory()
  })
  router.beforeEach((to) => {
  //子应用给基座发送数据
    dispatchEvent({
      url: `/****/****/#${to.fullPath}`,
      id: to.meta.id
    })
  })
  return router
}
export default useRouterCreate

4.在基座中的面包屑vue文件中,当监听到A应用给基座发送的数据后,通过判断A应用发送的id与面包屑的id做递归匹配,从而使得面包屑的层级按照配置文件中的层级去展示,达到C/B的效果。递归方法如下:

js 复制代码
/**
 *
 * @param {*} tree 路由配置文件
 * @param {*} targetUrl 匹配的id
 * @param {*} parents 面包屑格式
 * @returns
 */
function findNodeAndParent(tree, targetUrl, parents = []) {
  for (const node of tree) {
    // 将当前节点添加到父级数组中
    const currentParents = [
      ...parents,
      { name: node.name, id: node.id, path: node.path, url: node.url }
    ]

    if (node.id === targetUrl) {
      // 找到目标节点,返回它和所有的父级组件
      return currentParents
    }

    if (node.children) {
      // 如果有子节点,递归搜索子节点
      const result = findNodeAndParent(node.children, targetUrl, currentParents)
      if (result) {
        return result // 如果找到了,返回结果
      }
    }
  }
  return null // 如果未找到目标节点,返回null
}

发现的问题

因为micro-app支持不同框架的子应用,所以整个系统不仅包含了新开发的vue3项目,还嵌套了vue2的老项目,问题就出现在了vue2这些老项目中,首次进入vue2项目的某一菜单时,面包屑加载不出来,刷新浏览器也不行,但是再点击该项目中的另一个菜单时,就会加载出来了。总结来说就是初次加载vue2项目时,面包屑加载不出来,再次点击该项目下的其他菜单时,就都可以正常展示了。

排查问题

初步怀疑是加载顺序导致的,于是从main.js开始排查

js 复制代码
const appElId = '#子应用id';
const mount = () => {
  app = new Vue({
    router,
    store,
    i18n,
    render: h => h(App)
  }).$mount(appElId, true);
  //问题出现在了这里
  initCommunicate(appElId);
  
  // 路由处理
  addDataListener((data) => {
    i18n.mergeLocaleMessage(data.language, data.common);
    // 通过基座应用通知子应用进行路由跳转
    if (data.path && typeof data.path === 'string') {
      const path = data.path.replace(/^#/, '');
      // 当基座下发path时进行跳转
      if (path && path !== router.currentRoute.path) {
        router.push(path);
      }
    }
  }, true);
};
js 复制代码
let microSubApp = null;
/*
 * 是否是子应用实例
 */
function hasSubAppInstance() {
  return !!microSubApp;
}
/**
 * 初始化与基座应用的通信
 * @param appRoot
 * @return Boolean 是否初始化成功
 */
export function initCommunicate(appEl) {
  const appName = getAppName(appEl);
  const subApp = window[`__SUB_APP__${appName || ''}`] || window.microApp;
  if (subApp) {
    microSubApp = subApp;
  }
  return hasSubAppInstance();
}

发现原来当挂载子应用时,此时并未初始化与基座应用的通信,导致初始化未成功。此时函数hasSubAppInstance()的返回值为false。

js 复制代码
/**
 * 向基座应用发送数据
 * @param event 被发送的数据
 */
export function dispatchEvent(event) {
  if (hasSubAppInstance()) {
    //未执行
    microSubApp.dispatch(event);
  }
}

导致全局前置守卫中,dispatchEvent内部并未执行,所以没有将子应用的数据发送至基座,基座未接收到数据,导致面包屑未展示。

点击其他菜单的时候,此时已经初始化子应用与基座应用的通信,子应用可以正常的向基座发送数据,所以面包屑就可以成功展示了。

相关推荐
Moment7 分钟前
面试官:一个接口使用postman这些测试很快,但是页面加载很慢怎么回事 😤😤😤
前端·后端·面试
诗书画唱11 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel17 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子24 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构31 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep32 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss36 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风37 分钟前
html二次作业
前端·html
江城开朗的豌豆40 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵40 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae