React + Umi + qiankun的微前端构建,错误处理,父子应用通讯

1,747 阅读6分钟

简介

qiankun 是基于 single-SPA 二次封装的微前端框架,single-SPA 的原理是基于路由的变化在注册的节点挂载资源,并实现子应用的生命周期,而 qiankun 在此基础上还加入了样式隔离,JS 沙箱,资源预载等功能,且提供了 umijs/plugin-qiankun 插件供 umi 更便捷地部署微前端。

使用的技术栈为React+Umi+qiankun,由于qiankun的文档不够完善,踩了很多坑,以此篇记录一下Umi-qiankun微应用的基本构建流程。

配置基本流程

React+Umi项目构建

参考Umi官方文档:v3.umijs.org/zh-CN/docs/…

添加乾坤依赖

yarn add @umijs/plugin-qiankun -D

子应用注册

子应用的注册是在主应用中完成的,分为静态配置和动态配置两种方式

在.umirc.ts 中静态配置子应用

# .umirc.ts
export default defineConfig({
  title: 'MyProject',
  qiankun: {
    master: {
      apps: [
        {
          name: 'myApp', // 微应用名称
          entry: '//localhost:8057', // 微应用地址
        },
      ],
    },
  },
})

在 app.ts 中运行时动态配置子路由

除了静态配置注册子应用,还可以在app.ts运行时配置中,请求接口获取配置数据,使得子应用的注册变得可根据接口动态配置,例如:

# app.ts
export const qiankun = fetch('/config').then(({ apps }) => ({
  // 子应用配置
  apps,
  // 生命周期函数
  lifeCycles: {
    afterMount: (props) => {
      console.log(props);
    },
  },
}));

子应用加载

子应用注册完之后就可以页面中使用他们了,子应用的加载同样有基于路由加载和<MicroApp>组件化加载两种方式

基于路由加载

通过配置式路由的方式指定子应用加载时机,在访问对应路由时,自动加载子应用并执行其生命周期函数

routes: [
      { path: '/microApp', microApp: 'myApp' },
    ],

基于组件化加载

引入<MicroApp>组件,通过 name 属性把注册过的子组件挂载到自身,需要注意的时此时子组件的路由必须和父组件匹配,因为子组件也会按照父组件当前路由去寻址加载自身组件。

import { MicroApp } from 'umi';

export function MyPage() {

  return (
    <div>
      <div>
        <MicroApp name="myApp" />
      </div>
    </div>
  )
}

子应用配置

使用 qiankun 时无需在子应用中做过多的配置,主要是对子组件的生命周期进行控制

基本配置

# .umirc.ts
  qiankun: {
    slave: {},
  },

在 package.json 中删除private:true字段,添加name字段,子应用配置完毕。

生命周期

在 app.ts 中引入 qiankun 配置子组件生命周期

# app.ts
export const qiankun = {
  // 应用加载之前
  async bootstrap(props: any) {
    console.log('初始化', props);
  },
  // 应用 render 之前触发
  async mount(props: any) {
    console.log('已挂载', props);
  },
  // 应用卸载之后触发
  async unmount(props: any) {
    console.log('已卸载', props);
  },
};

至此一个简单的微前端架构就构建好了,实际项目使用中除了以上基本内容外,还需要考虑到主应用和子应用之间的通讯,以及错误处理等操作。

主应用及子应用间通讯

在 Umi 同样提供了两种种方式供父子应用通讯,分别是:

  • 配合useModel使用
  • 基于 Props 传递

使用 useModel 来传递数据

useModel是 Umi 基于 hooks 范式提供的简易全局数据管理方案,不仅能全局管理状态,还能像 hooks 一样全局复用逻辑,在 Umi-qiankun 中推荐采用这种方式来进行父子组件通讯,其分为主应用传数据和子应用拿数据两部分:

主应用传递数据:

使用<MicroApp>时直接通过组件 props 传递,例如:

import { MicroApp } from 'umi';

const MainApp = (props) => {

  // 待传递的数据
  const [myData, setMyData] = useState({
    name:'主应用数据'
  });

  return (
    <div>
      <MicroApp name="myMicroApp" myData={myData} />
    </div>
  );
};

当使用路由加载子应用时,传递数据需要到app.ts中设置全局 Model 供子应用消费,如下:

# app.ts
export function useQiankunStateForSlave() {

  const [masterState, setMasterState] = useState({
    name: '全局数据数据',
  });

  // 全局数据
  return {
    masterState,
    setMasterState,
  };
}

子应用取得数据:

通过上面两种方式,数据已经传递到了子应用中,现在我们需要到子应用中取得数据:

首先是使用 useModel 拿取数据:

import { useModel } from 'umi';

const MyMicroApp = (props)=>{

  // 取得数据
  const masterProps = useModel('@@qiankunStateFromMaster');
  console.log('子应用拿到的数据', masterProps)

  return <div>...</div>
}

此外还可以使用 umi 提供的高阶组件connectMaster更方便的拿取数据:

import { connectMaster } from 'umi';

const MyMicroApp = (props) => {

  console.log('数据透传到了props中', props);

  return <div>...</div>;
};

export default connectMaster(MyMicroApp);

基于 Props 传递数据

使用这种方法主应用需要到umirc.ts中配置式地传递数据,例如:

# umirc.ts
export default defineConfig({
  title: 'mainApp',
  qiankun: {
    master: {
      apps: [
        {
          name: 'microApp',
          entry: '//localhost:8888',
          props: {
            name: '你要传递的数据',
            descript:'通过props以对象的方式传递',
          },
        },
      ],
    },
  },
})

同时也可以在运行时配置app.ts中传递数据:

# app.ts
export const qiankun = fetch('/config').then((config) => {
  return {
    apps: [
      {
        name: 'mdShow', // 微应用名称
        entry: '//localhost:8057', // 微应用入口文件
        props: {
          name: '你要传递的数据',
          descript:'通过props以对象的方式传递',
          testFun:(data:any)=>{
            console.log('此方式可以传递函数',data)
          }
        },
      },
    ],
  };
});

在子应用消费数据时,只能到其 app.ts 中从生命周期函数获取数据:

# app.ts
export const qiankun = {

  // 应用加载之前
  async bootstrap(props: any) {
    console.log('子应用初始化', props);
  },

  // 应用 render 之前触发
  async mount(props: any) {
    console.log('子应用已挂载', props);
  },

  // 应用卸载之后触发
  async unmount(props: any) {
    console.log('子应用已卸载', props);
  },
};

此时子应用生命周期函数拿到的 props ,即包含了主应用传递的数据。

可以看到次方法传值不够灵活,是配置化传值且需要到生命周期函数中进行消费,仅特定场景可以发挥作用。且在使用时还有以下注意的点:

  • 不要以 name 作为属性名,因为默认接收的 props 中包含了主应用中注册时声明的 name 字段,会被覆盖
  • 主应用在 umirc.ts 中配置传递参数时,无法传递函数

所以在使用时建议采取 useModel 的方式进行父子应用通讯,采用useState和useModel方案即可实现父子应用及兄弟应用间的通讯

错误处理

Umi-qiankun 的错误处理,一般是在子应用加载失败,或者子应用中出现不可预知的错误时进行错误处理,所以是在主应用中进行的。qiankun 提供了 single-SPA 中的addErrorHandler和自封装的addGlobalUncaughtErrorHandler来进行错误处理,可以在app.ts中配置:

import * as qiankun from 'qiankun';
import { history } from 'umi';

qiankun.addErrorHandler((err) => {
  console.log('捕获错误', err);
});

qiankun.addGlobalUncaughtErrorHandler((err) => {
  history.push('/404');
  console.log('捕获未知错误', err);
});

export { qiankun };

踩坑记录

  • qiankun 会一次性加载所有的子应用,如果有子应用不需要使用时应当清除注册,否则会产生报错(必须是清除注册)
  • 使用 MicroApp 组件化引入的时候,子应用路由应该和主应用保持一致