微前端应用(qiankun+umi+antd)

目录

1.微前端介绍以应用选型

1.1什么是微前端?

1.2技术选择

2.开始使用

2.1配置父应用

2.2配置子应用

2.3引入子应用

2.3.1路由绑定引入子应用

2.3.2 组件引入子应用

2.3.3 组件引入子应用

2.4子应用之间跳转

3.子应用生命周期

3.1父应用配置生命周期钩子

3.2子应用配置生命周期钩子

4.父子应用通信

4.1基于 useModel() 的通信

4.1.1主应用透传数据

4.1.2子应用消费数据


1.微前端介绍以应用选型

1.1什么是微前端?

微前端是一种前端架构模式,它将前端应用程序拆分成多个小型的、独立开发、独立部署的子应用,然后将这些子应用组合成一个大型的、复杂的前端应用。每个子应用都有自己的技术栈、独立的代码库、独立的开发、测试和部署流程,并且可以独立运行、测试和部署。

微前端的目的是解决大型前端应用程序在开发、测试和部署等方面的复杂性和困难。通过将前端应用程序拆分成多个独立的子应用,可以实现团队的分工协作、提高开发效率、降低代码耦合性、提高代码可维护性和可测试性。同时,微前端也可以实现应用程序的增量更新、灰度发布、动态加载和懒加载等功能,从而提高应用程序的性能和用户体验。

1.2技术选择

主应用:umi(Ant Design Pro)

子应用:umi(Ant Design Pro)

父应用和子应用其实都是独立的前端项目,父应用可以在内部引入子应用,子应用也可以在自己内部继续引入孙子应用

2.开始使用

2.1配置父应用

首先需要配置父应用,注册子应用的相关信息,这样父应用才能识别子应用并在内部引入。

注册子应用的方式主要有两种:

  • 插件注册子应用。
  • 运行时注册子应用。

以上两种方式都是在 .umirc.ts 中注册子应用,但是使用的插件不同,从而导致了使用方式的不同。

  1. 插件注册子应用:

使用插件注册子应用时,需要安装 @umijs/plugin-qiankun 插件,并在 .umirc.ts 中进行配置,如下:

import { defineConfig } from 'umi';

export default defineConfig({
  qiankun: {
    slave: {},
    master: {
      apps: [
        {
          name: 'reactApp', // 唯一 id
          entry: 'http://localhost:8091', // html entry
        },
      ],
      // sandbox: false,
    },
  },
});

qiankun 配置中,slave 为子应用的配置,可以在这里配置子应用的名称、入口、路由、公共依赖等信息。该插件会自动构建子应用的入口文件、修改 webpack 配置等。

注意:没有.umirc.ts文件就写在config.ts中,上述中如果不添加slave: {}配置信息,会报错:Unhandled Rejection (Error): register failed, invalid key useQiankunStateForSlave from plugin ../../app.tsx.如果有这个报错请参考这种方法

  1. 运行时注册子应用:

运行时注册子应用时,不需要安装额外的插件,只需要在 .umirc.ts 中进行配置,如下:

import { defineConfig } from 'umi';

export default defineConfig({
  // 省略其他配置
  routes: [
    {
      path: '/',
      component: '@/layouts/index',
      routes: [
        {
          path: '/',
          component: '@/pages/index',
          microApp: 'sub-app', // 运行时注册子应用
        },
      ],
    },
  ],
});

routes 中配置 microApp 字段即可运行时注册子应用。在这里可以配置子应用的名称、入口、路由等信息。

两种方式的目的都是为了在主应用中注册子应用,只是使用的插件和配置方式有所不同。如果使用 @umijs/plugin-qiankun 插件,则可以自动构建子应用的入口文件、修改 webpack 配置等,使用起来更加方便。如果不使用插件,则需要手动配置子应用的入口、路由等信息。

2.2配置子应用

子应用需要导出必要的生命周期钩子,供父应用在适当的时机调用。

假设您的子应用项目基于 Umi 开发引入了 qiankun 插件。如果没有,可以按照此教程进行配置。

修改子应用的 Umi 的配置文件,添加如下内容:

// .umirc.ts
export default {
  qiankun: {
    slave: {},
  },
};

这样,微前端插件会自动在项目中创建好 Qiankun 子应用所需的生命周期钩子和方法.

本地调试时,主应用中点击子应用的路由地址,报错”Unhandled Rejection (TypeError): Failed to fetch”,

要解决这个问题,有两种方法可以尝试:

  1. 配置子应用的跨域规则

在子应用的配置中,通过配置跨域规则来允许主应用访问子应用的资源。具体来说,可以在子应用的配置文件中(例如 umi 项目的 config/config.ts 或 config/config.prod.ts 文件)加入如下配置:

export default {
  // ...
  devServer: {
    // 允许跨域访问的域名,如果有多个可以用逗号隔开
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // ...
};

这个配置会在子应用启动时,自动为子应用启动一个 devServer 服务,并允许跨域访问。

  2.配置主应用代理

export default {
  // ...
  proxy: {
    '/app1': {
      target: 'http://localhost:8001', // 子应用1的服务地址
      changeOrigin: true,
      pathRewrite: {
        '^/app1': '',
      },
    },
    '/app2': {
      target: 'http://localhost:8002', // 子应用2的服务地址
      changeOrigin: true,
      pathRewrite: {
        '^/app2': '',
      },
    },
    // ...
  },
  // ...
};

这个配置将会把以 /app1 或 /app2 开头的请求,分别代理到子应用1和子应用2所在的服务上。注意,在使用代理时,要确保子应用的服务已经启动并监听了对应的端口号。

2.3引入子应用

在父应用中引入子应用,插件提供了三种不同实现的方式:

  • 路由绑定引入子应用。
  • <MicroApp /> 组件引入子应用。
  • <MicroAppWithMemoHistory /> 组件引入子应用。

2.3.1路由绑定引入子应用

手动配置 config.ts 文件中的 routes 项,通过路由的方式绑定子应用。何时使用:

  • 子应用包含完整的路由切换逻辑时。
  • 父子应用路由相互关联时。

现在,可以配置父应用下的一个子应用"react子应用",路由如下:

{
    name: 'react子应用',
    path: '/subreact',
    microApp: 'reactApp',
    microAppProps: {
      autoSetLoading: true,
    },
    routes: [
      {
        name: 'Welcome',
        icon: 'smile',
        path: '/subreact/Welcome',
        
      },
       {
        name: 'table-list',
        icon: 'table',
        path: '/subreact/list',
         // 如果想要将 /subreact/list 下所有子路由都关联给微应用 app1,可以带上 * 通配符
      },
    ],
  },

配置好后,子应用的路由 base 会在运行时被设置为主应用中配置的 path。 例如,在上面的配置中,我们指定了 react子应用 关联的 path 为 /subreact/Welcome,假如 app1 里有一个路由配置为 /user,当我们想在父应用中访问 /user 对应的页面时,浏览器的 url 需要是 base + /user,即 /subreact/user 路径,否则子应用会因为无法匹配到正确的路由而渲染空白或404页面。

可以手动修改路由配置信息:


if (window.__POWERED_BY_QIANKUN__) {
  routesData.map(item => {
    if (item.path.includes('/')) {
      item.path = '/subreact' + item.path
    }
    if (item.redirect) {
      item.redirect = '/subreact' + item.redirect
    }
    return item
  })
}

2.3.2<MicroApp /> 组件引入子应用

通过 <MicroApp /> 组件加载(或卸载)子应用。何时使用:

  • 子应用包含完整的路由切换逻辑时。
  • 父子应用路由相互关联时。

现在,我们想在父应用的某个页面中引入子应用 app1,可以编写代码如下:

import { MicroApp } from 'umi';
 
export default function Page() {
  return <MicroApp name="subreact" />;
};

使用该方式引入子应用时,父子应用的路由将一一对应。例如,当父应用路由为 /some/page 时,子应用路由同样为 /some/page。切换子应用路由时,父应用将同步切换。

如果父应用的路由包含前缀,可以通过配置 base 属性保证父子应用的路由正确对应。例如,父应用路由为 /main-react/welcome 时,我们希望子应用的路由为 /subreact/welcome,可以修改代码如下:

import { MicroApp } from 'umi';
 
export default function Page() {
  return <MicroApp name="reactApp" base="/main-react" />
};

2.3.3<MicroAppWithMemoHistory /> 组件引入子应用

通过 <MicroAppWithMemoHistory /> 组件加载(或卸载)子应用。何时使用:

  • 仅使用子应用的指定路由时。
  • 父子应用路由相互独立时。

<MicroAppWithMemoHistory /> 组件是 <MicroApp /> 组件的变体,您需要显式提供 url 属性作为子应用的路由。当父应用的路由发生变化时,子应用的路由不会改变

现在,我们想在父应用的某个组件内部引入 subreact 子应用,子应用的路由为 /subreact/welcome,可以编写代码如下:

<MicroAppWithMemoHistory name="reactApp" url="/welcome" />

2.4子应用之间跳转

这个我暂时未尝试成功,会报错MicroAppLink 导出错误问题,成功的小伙伴欢迎补充.下面是具体内容:

如果子应用通过路由绑定的方式引入,在其它子应用的内部,可以使用 <MicroAppLink /> 跳转到对应的路由。以子应用 app1 和 app2 为例:

// 在 app1 中
import { MicroAppLink } from 'umi';
 
export default function Page() {
  return (
    <>
      {/* 跳转链接为 /app2/home */}
      <MicroAppLink name="app2" to="/home">
        <Button>go to app2</Button>
      </MicroAppLink>
    </>
  );
}

在上面的例子中,点击按钮后,父应用的路由变为 /app2/home,渲染子应用 app2 内部路由为 /home 的页面。同理,如果想要从子应用 app2 回到子应用 app1,可以编写代码如下:

// 在 app2 中
import { MicroAppLink } from 'umi';
 
export default function Page() {
  return (
    <>
      {/* 跳转链接为 /app1/project/home */}
      <MicroAppLink name="app1" to="/home"> 
        <Button>go to app1</Button>
      </MicroAppLink>
    </>
  );
}

您也可以从子应用跳转到父应用的指定路由:

// 在子应用中
import { MicroAppLink } from 'umi';
 
export default function Page() {
  return (
    <>
      {/* 跳转链接为 /table */}
      <MicroAppLink isMaster to="/table">
        <Button>go to master app</Button>
      </MicroAppLink>
    </>
  );
}

补充:可以通过路由引用和组件引用同一个子应用

3.子应用生命周期

3.1父应用配置生命周期钩子

在父应用的 src/app.ts 中导出 qiankun 对象进行全局配置,所有的子应用都将实现这些生命周期钩子:

// src/app.ts
export const qiankun = {
  lifeCycles: {
    // 所有子应用在挂载完成时,打印 props 信息
    async afterMount(props) {
      console.log(props);
    },
  },
};

3.2子应用配置生命周期钩子

在子应用的 src/app.ts 中导出 qiankun 对象,实现生命周期钩子。子应用运行时仅支持配置 bootstrapmount 和 unmount 钩子:

/ src/app.ts
export const qiankun = {
  // 应用加载之前
  async bootstrap(props) {
    console.log('app1 bootstrap', props);
  },
  // 应用 render 之前触发
  async mount(props) {
    console.log('app1 mount', props);
  },
  // 应用卸载之后触发
  async unmount(props) {
    console.log('app1 unmount', props);
  },
};

会将引用子组件时传递给子组件的函数和参数都打印出来,这个下面组件传递信息会讲到,且props中包括子应用的信息,如下:

4.父子应用通信

父子应用间的通信有两种实现的方法:

  • 基于 useModel() 的通信。这是 Umi 推荐的解决方案。
  • 基于配置的通信。

4.1基于 useModel() 的通信

该通信方式基于 数据流 插件,此插件已经内置于 @umi/max 解决方案当中。

该通信方式需要子应用基于 Umi 开发引入了该数据流插件

关于此插件的详细介绍可见数据流指南

4.1.1主应用透传数据

如果通过路由的模式引入子应用,则需要在父应用的 src/app.ts 里导出一个名为 useQiankunStateForSlave() 的函数,该函数的返回值将传递给子应用:

// src/app.ts
export function useQiankunStateForSlave() {
  const [globalState, setGlobalState] = useState<any>({
    slogan: 'Hello MicroFrontend',
  });
 
  return {
    globalState,
    setGlobalState,
  };
}

如果通过组件的模式引入子应用,直接将数据以组件参数的形式传递给子应用即可:


import React, { useState } from 'react';
import { MicroApp } from 'umi';
 
export default function Page() {
  const [globalState, setGlobalState] = useState<any>({
    slogan: 'Hello MicroFrontend',
  });
 
  return (
    <MicroApp
      name="app1"
      globalState={globalState}
      setGlobalState={setGlobalState}
    />
  );
};

4.1.2子应用消费数据

子应用会自动生成一个全局的 Model,其命名空间为 @@qiankunStateFromMaster。通过 useModel() 方法,允许子应用在任意组件中获取并消费父应用透传的数据,如下所示:

import { useModel } from 'umi';
 
export default function Page() {
  const masterProps = useModel('@@qiankunStateFromMaster');
  return <div>{JSON.stringify(masterProps)}</div>;
};

或者可以通过高阶方法 connectMaster() 来获取并消费父应用透传的数据,如下所示:

import { connectMaster } from 'umi';
 
function MyPage(props) {
  return <div>{JSON.stringify(props)}</div>;
}
 
export default connectMaster(MyPage);

子应用也可以在生命周期钩子中能够获取并消费得到的 props 属性,根据需求实现对应的生命周期钩子即可。

特别的,当父应用使用 <MicroApp /> 或 <MicroAppWithMemoHistory /> 组件的方式引入子应用时,会额外向子应用传递一个 setLoading() 方法,允许子应用在合适的时机执行,标记子应用加载为完成状态:

const masterProps = useModel('@@qiankunStateFromMaster');
masterProps.setLoading(false);
 
// 或者
function MyPage(props) {
  props.setLoading(false);
}
connectMaster(MyPage);

注:setLoading() 方法可以和自定义加载配套使用,在主应用中通过组件引入子应用时,自定义loader,如下:

//CustomLoader.tsx
import { Alert, Spin } from 'antd';
import * as React from 'react';

interface ICustomLoaderProps {
    loading: boolean
}


const CustomLoader: React.FC<ICustomLoaderProps> = (props) => {
    const { loading } = props
    return <Spin spinning={loading}>
    </Spin>;
};

export default CustomLoader;
import CustomLoader from '@/components/CustomLoader';
<MicroAppWithMemoHistory name="reactApp"
          url="/welcome"
          loader={(loading) => <CustomLoader loading={loading} />} />

下面例举一下组件引用方式时,子应用通过父应用传递过来的方法修改父应用中的值:

const masterProps = useModel('@@qiankunStateFromMaster');

const onChangeGlobalState = () => {
  const updatedGlobalState = {
    ...masterProps?.globalState,
    slogan: '我通过子应用成功修改父应用中的值',
  };

  masterProps?.setGlobalState(updatedGlobalState);
};

注意:

子应用中使用 useModel 获取到的 masterProps 对象包含了从主应用中传递过来的 globalStatesetGlobalState 方法。然而,直接使用 setGlobalState 修改 globalState 时可能无法实现更新。

这是因为 masterProps 中包含的 globalState 是主应用中的状态,子应用不能直接修改主应用中的状态。子应用只能通过 setGlobalState 方法向主应用传递更新后的状态,并让主应用自己进行更新。因此,在子应用中使用 setGlobalState 方法时,需要将整个更新后的状态对象作为参数传递给 setGlobalState 方法。而不能直接修改 globalState 对象中的某个属性值。

正确的做法是,在子应用中使用 setGlobalState 方法时,先从 masterProps 中获取当前的 globalState 对象,然后将需要更新的属性值进行修改,最后将整个更新后的 globalState 对象传递给 setGlobalState 方法。

或者可以使用下面这种方法:

import { useModel } from 'umi';
import { Button } from 'antd';

function SubApp() {
  const { globalState, setGlobalState } = useModel('@@qiankunStateFromMaster');
  const handleClick = () => {
    setGlobalState({
      ...globalState,
      slogan: 'New Slogan from Sub App',
    });
  };
  return (
    <div>
      <h1>{globalState.slogan}</h1>
      <Button onClick={handleClick}>更改父应用globalState</Button>
    </div>
  );
}

export default SubApp;

参考文档:

微前端 | UmiJS

### 如何在 Ant Design Pro 中实现微前端架构 #### 实现微前端架构的意义 为了应对复杂的企业级应用需求,采用微前端架构能够显著提高项目的可维护性和灵活性。这种架构允许不同的团队独立开发、部署各自负责的应用模块,同时这些模块可以在同一个页面中无缝协作[^3]。 #### 微前端的技术选型 Ant Design Pro 基于 React 构建,并集成了 umi 和 dva 等先进的前端框架和技术栈。umi 提供了强大的路由管理和插件化能力,这为实施微前端架构奠定了坚实的基础;dva 则简化了状态管理流程,有助于保持各个子应用之间的松耦合关系[^2]。 #### 创建主应用程序 首先,在主应用程序中安装 `qiankun` 插件来支持微前端功能: ```bash npm install qiankun --save ``` 接着修改配置文件 `.umirc.ts` 或者 `_config/@umijs/config.js` 来注册子应用: ```typescript // .umirc.ts or _config/@umijs/config.js export default { plugins: [ ['umi-plugin-react', {}], 'qiankun', ], qiankun: { master: { apps: [ { name: 'app1', entry: '//localhost:8001' }, { name: 'app2', entry: '//localhost:8002' } ] } } } ``` #### 子应用的改造 对于每一个作为微前端一部分的小型React应用来说, 需要将其转换成一个可以被QianKun识别并加载的形式。具体做法是在每个子应用根目录下新建或编辑现有的入口文件(main.jsx),按照如下方式进行调整: ```jsx import ReactDOM from "react-dom"; import App from "./App"; if (!window.__POWERED_BY_QIANKUN__) { ReactDOM.render(<App />, document.getElementById('root')); } /** * bootstrap 只会在应用初始化的时候调用一次,下次应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 */ export async function bootstrap() {} /** * 应用每次进入都会调用 mount 方法,通常我们在这里做一些数据的初始化操作。 */ export async function mount(props) { console.log('props from main framework', props); ReactDOM.render( <App />, document.querySelector('#root') ); } /** * 应用每次退出都会调用 unmount 方法,通常在这里我们会卸载当前应用的一些事件监听器等。 */ export async function unmount(props) { await ReactDOM.unmountComponentAtNode(document.querySelector('#root')); } ``` 上述代码片段展示了如何使子应用兼容 QianKun 的生命周期钩子函数(`bootstrap`, `mount`, `unmount`),从而让其能够在主应用环境中正常工作[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值