微前端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()
复制代码


总结


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


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

相关文章
|
21天前
|
资源调度 前端开发 测试技术
前端工程化实践:从零搭建现代化项目构建流程
【4月更文挑战第6天】本文介绍了前端工程化的概念和重要性,包括模块化、自动化、规范化和CI/CD。接着,讨论了选择合适的工具链,如包管理器、构建工具和测试框架。然后,详细阐述了如何从零开始搭建一个基于React的现代化项目构建流程,涉及初始化、代码规范、测试、CSS处理、代码分割和CI/CD配置。最后,提到了持续优化与迭代的方向,如性能优化、类型检查和微前端。通过这样的实践,开发者可以提升开发效率和代码质量,为项目长远发展奠定基础。
27 0
|
20天前
|
前端开发 数据可视化 JavaScript
探索前端可视化开发:低代码平台原理与实践
【4月更文挑战第7天】本文探讨了低代码平台在前端开发中的应用,介绍了其模型驱动、组件化和自动化部署的原理,强调了提升效率、降低技术门槛、灵活适应变更和保证一致性等优势。建议开发者明确适用场景,选择合适平台,并培养团队低代码技能,同时规划与现有技术栈的融合,实施持续优化治理。低代码平台正改变开发格局,为业务创新和数字化转型提供新途径。
46 0
|
1天前
|
前端开发 JavaScript 开发者
前端技术栈:探索现代Web开发的核心要素与代码实践
前端技术栈:探索现代Web开发的核心要素与代码实践
7 1
|
1天前
|
前端开发 JavaScript 开发工具
前端技术栈:构建现代Web应用的基石与实践
前端技术栈:构建现代Web应用的基石与实践
9 3
|
3天前
|
前端开发 JavaScript 开发者
【专栏】前端工程化实践与构建工具比较:Webpack、Rollup等
【4月更文挑战第27天】本文探讨了前端工程化的重要性,强调构建工具在其中的角色,如Webpack和Rollup。Webpack以其灵活性和插件系统适用于复杂SPA项目,建议开发者理解其核心概念并优化性能。Rollup则专注于JavaScript模块打包,生成更小、更快的代码,适合小型至中型项目和库创建,以其Tree-shaking技术减小代码体积。开发者应根据项目需求、团队技术和生态选择合适工具,掌握核心原理以提升开发效率和质量。
|
18天前
|
敏捷开发 前端开发 JavaScript
实践总结|前端架构设计的一点考究(上)
实践总结|前端架构设计的一点考究(上)
36 0
|
18天前
|
SQL 前端开发 JavaScript
实践总结|前端架构设计的一点考究(中)
实践总结|前端架构设计的一点考究(中)
34 0
|
18天前
|
设计模式 开发框架 前端开发
实践总结|前端架构设计的一点考究(下)
实践总结|前端架构设计的一点考究(下)
43 0
|
18天前
|
监控 前端开发 小程序
微信小程序全栈开发中的前端工程化实践
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的前端工程化实践,旨在提升开发效率和体验。重点包括代码规范与架构设计(模块化、组件化、MVC模式)、自动化构建与部署(使用Webpack、Git和CI工具)、前端框架与库(如Vue.js、React、Angular)以及性能优化策略(代码、资源优化和性能监控)。通过这些实践,开发者能更高效地掌握小程序开发,打造高质量应用。
|
20天前
|
JavaScript 前端开发 IDE
TypeScript在大型前端项目中的价值与实践策略
【4月更文挑战第7天】本文探讨了TypeScript在大型前端项目中的价值和实践策略。 TypeScript通过静态类型检查、代码提示、接口与泛型提高代码质量和开发效率。在大型项目中,可采用逐步迁移策略,制定类型规范,利用IDE特性,并维护类型定义文件。通过CI/CD和培训分享,团队能充分发挥TypeScript优势,提升项目可维护性、可扩展性和开发效率。
18 0