微前端x重构实践落地总结(下)

简介: 大家好,我是海怪。最近换到了新部门,在做智能平台相关的内容。我接到的第一个任务就是把以前前端的项目重构一次。 说是重构,不如说是重写一遍。因为原来的项目是 ant-design-vue + vue 全家桶,要切换成 ant-design + ant-design-pro + react 全家桶。 更让人头疼的是,产品经理并不会让我们有大把大把时间专门搞重构,我们要边重构边做需求。在这样的挑战下,我想到了微前端解决方案,下面就跟大家分享这次 微前端在重构上的落地实践吧。

image.png

主子应用状态管理


老项目(主应用)用到了 vuex 全局状态管理,所以新项目页面(子应用)里有时需要更改主应用里的状态,这里我用了 qiankun 的 globalState 来处理。

image.png


首先在 Container 里创建了 globalActions,再监听 vuex 状态变更,每次变更都通知子应用,同时把 vuex 的 commitdispatch 函数传给子应用:

import {initGlobalState, registerMicroApps, start} from 'qiankun'
const globalActions = initGlobalState({
  state: {},
  commit: null,
  dispatch: null,
});
export default {
  name: "Container",
  props: {
    visible: {
      type: Boolean,
      defaultValue: false,
    }
  },
  mounted() {
    const { dispatch, commit, state } = this.$store;
    registerMicroApps([
      {
        name: 'microReactApp',
        entry: '//localhost:3000',
        container: '#micro-app-container',
        activeRule: '/#/micro-react-app',
        // 初始化时就传入主应用的状态和 commit, dispatch
        props: {
          state,
          dispatch,
          commit,
        }
      },
    ])
    start()
    // vuex 的 store 变更后再次传入主应用的状态和 commit, dispatch
    this.$store.watch((state) => {
      console.log('state', state);
      globalActions.setGlobalState({
        state,
        commit,
        dispatch
      });
    })
  },
}
复制代码


子应用里接收主应用传来的 statecommit 以及 dispatch 函数,同时新起一个 Context,把这些东西都放到 MicroAppContext 里。(Redux 因为不支持存放函数这种 nonserializable 的值,所以只能先存到 Context 里)

// 渲染
function render(props: any) {
  const { container, state, commit, dispatch } = props;
  const value = { state, commit, dispatch };
  const root = (
    <HashRouter basename={basename}>
      <MicroAppContext.Provider value={value}>
        <App />
      </MicroAppContext.Provider>
    </HashRouter>
  );
  ReactDOM.render(root, container
    ? container.querySelector('#root')
    : document.querySelector('#root'));
}
// mount 时监听 globalState,只要一改再次渲染 App
export async function mount(props: any) {
  console.log('[micro-react-app] mount', props);
  props.onGlobalStateChange((state: any) => {
    console.log('[micro-react-app] vuex 状态更新')
    render(state);
  })
  render(props);
}
复制代码


这样一来,子应用也可以通过 commit,和 dispatch 来更改主应用的值了。

const OrderList: FC = () => {
  const { state, commit } = useContext(MicroAppContext);
  return (
    <div>
      <h1 className="title">【微应用】订单列表</h1>
      <div>
        <p>主应用的 Counter: {state.counter}</p>
        <Button type="primary" onClick={() => commit('increment')}>【微应用】+1</Button>
        <Button danger onClick={() => commit('decrement')}>【微应用】-1</Button>
      </div>
    </div>
  )
}

image.png

当然了,这样的实践也是我自己 “发明” 的,不知道这是不是一个好的实践,我只能说这样能 Work。


全局变量报错


另一个问题就是当子应用隐式使用全局变量时,import-html-entry 执行 JS 时会直接爆炸。比如微应用有如下 <script> 的代码:

var x = {}; // 报错,要改成 window.x = {};
x.a = 1 // 报错,要改成 window.x.a = 1;
function a() {} // 要改成 window.a = () => {}
a() // 报错,要改成 window.a()
复制代码

在主应用加载微应用后,上面的 xa 全都会报 xxx is undefined,这是因为 qiankun 在加载微应用时,会执行这部分 JS 代码,而此时 var 声明的变量不再是全局变量,其他的文件无法获取到。


解决方法就是使用 window.xxx 来显式定义/使用全局变量。具体可见 Issue: 子应用全局变量 undefined


主应用切换路由时不更新子应用路由


只要主子应用都用上了 Hash 路由,那么很大概率会遇到这个问题。

比如你主应用有 /micro-app/home/micro-app/user 两个路由,actvieRule/#/micro-app,子应用也有对应的 /micro-app/home/micro-app/user 两个路由。


那么如果 在主应用里/micro-app/home 切换到 /micro-app/user,会发现子应用的路由并没有改变。但如果你 在主应用的子应用里 去切换,那么就能切换成功。

这是因为在主应用切换路由时不是通过 location.url 这种可以触发 hash change 事件的方式来变更路由,而 react-router 只监听了 hash change 事件,所以当主应用切换路由时,没有触发 hash change 事件,导致子应用的监听不到路由变化,也就不会做页面切换了。


具体可见:Issue: 加载子应用正常,但主应用切换路由,子应用不跳转,浏览器返回前进可触发子应用跳转


解决方法很简单,下面三选一:

  • 将 vue 主应用中的 Link 超链方式替换成原生的 a 标签,从而触发浏览器的 hash change 事件
  • 主应用手动监听路由变更,同时手动触发 hash change 事件
  • 主应用跟子应用都改用 browser history 模式


加载状态


主应用在加载子应用时还是需要不少时间的,所以最好要展示一个加载中的状态,qiankun 正好提供了一个 loader 回调来让我们控制子应用的加载状态:

<div class="container" :style="{ height: visible ? '100%' : 0 }">
  <a-spin v-if="loading"></a-spin>
  <div id="micro-app-container"></div>
</div>
复制代码
registerMicroApps([
  {
    name: 'microReactApp',
    entry: '//localhost:3000',
    container: '#micro-app-container',
    activeRule: '/#/micro-react-app',
    props: {
      state,
      dispatch,
      commit,
    },
    loader: (loading) => {
      this.loading = loading // 控制加载状态
    }
  },
])
start()
复制代码


总结


总的来说,微前端在解构巨石应用的帮助真的很大。像我们这种要重构整个应用的情况,部门肯定不会先暂停业务,给开发一整个月来专门重构的,只能在评新需求的时候多给你一两天时间而已。


微前端就可以解决重构的过程中边做新需求边重构的问题,使得新老页面都能共存,不会一下子整个业务都停掉来做重构工作。

相关文章
|
1天前
|
编解码 前端开发 UED
探索无界:前端开发中的响应式设计深度解析与实践####
【10月更文挑战第29天】 本文深入探讨了响应式设计的核心理念,即通过灵活的布局、媒体查询及弹性图片等技术手段,使网站能够在不同设备上提供一致且优质的用户体验。不同于传统摘要概述,本文将以一次具体项目实践为引,逐步剖析响应式设计的关键技术点,分享实战经验与避坑指南,旨在为前端开发者提供一套实用的响应式设计方法论。 ####
20 4
|
14天前
|
人工智能 资源调度 数据可视化
【AI应用落地实战】智能文档处理本地部署——可视化文档解析前端TextIn ParseX实践
2024长沙·中国1024程序员节以“智能应用新生态”为主题,吸引了众多技术大咖。合合信息展示了“智能文档处理百宝箱”的三大工具:可视化文档解析前端TextIn ParseX、向量化acge-embedding模型和文档解析测评工具markdown_tester,助力智能文档处理与知识管理。
|
23天前
|
前端开发 JavaScript 开发者
构建工具对比:Webpack与Rollup的前端工程化实践
【10月更文挑战第11天】本文对比了前端构建工具Webpack和Rollup,探讨了它们在模块打包、资源配置、构建速度等方面的异同。通过具体示例,展示了两者的基本配置和使用方法,帮助开发者根据项目需求选择合适的工具。
18 3
|
1月前
|
前端开发 JavaScript 开发者
利用代码分割优化前端性能:高级技巧与实践
【10月更文挑战第2天】在现代Web开发中,代码分割是优化前端性能的关键技术,可显著减少页面加载时间。本文详细探讨了代码分割的基本原理及其实现方法,包括自动与手动分割、预加载与预取、动态导入及按需加载CSS等高级技巧,旨在帮助开发者提升Web应用性能,改善用户体验。
|
28天前
|
前端开发 JavaScript 开发者
深入解析前端开发中的模块化与组件化实践
【10月更文挑战第5天】深入解析前端开发中的模块化与组件化实践
22 1
|
1月前
|
前端开发 JavaScript API
前端开发趋势与实践:拥抱Web Components
前端开发趋势与实践:拥抱Web Components
35 4
|
1月前
|
前端开发 JavaScript 安全
前端开发趋势与实践:构建现代Web应用的探索
【10月更文挑战第1天】前端开发趋势与实践:构建现代Web应用的探索
32 2
|
1月前
|
存储 前端开发 JavaScript
前端技术深度探索:从基础到现代框架的实践之旅
前端技术深度探索:从基础到现代框架的实践之旅
28 2
|
21天前
|
JavaScript 前端开发 Docker
拿下奇怪的前端报错(二):nvm不可用报错`GLIBC_2.27‘‘GLIBCXX_3.4.20‘not Found?+ 使用docker构建多个前端项目实践
本文介绍了在多版本Node.js环境中使用nvm进行版本管理和遇到的问题,以及通过Docker化构建流程来解决兼容性问题的方法。文中详细描述了构建Docker镜像、启动临时容器复制构建产物的具体步骤,有效解决了不同项目对Node.js版本的不同需求。
|
2月前
|
缓存 前端开发 JavaScript
优化前端性能:关键策略与实践
在现代web开发中,前端性能优化至关重要。本文探讨了提升用户体验、转化率及降低服务器负载的关键策略,包括压缩资源文件、利用浏览器缓存、减少HTTP请求、异步加载、使用CDN、优化CSS/JavaScript执行、优化第三方脚本等,并介绍了Webpack/Rollup模块打包、HTTP/2特性、性能预算及Lighthouse/WebPageTest测试工具的应用。通过这些方法,可显著提高网站性能。