第一节:应用搭建
什么是 UmiJS?
Umi 是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由。然后配以生命周期完善的插件体系,支持各种功能扩展和业务需求。
什么是 qiankun?
Qiankun enables you and your teams to build next-generation and enterprise-ready web applications leveraging Micro Frontends. It is inspired by and based on single-spa.
A quick recap about the concept of Micro Frontends:
Techniques, strategies and recipes for building a modern web app with multiple teams using different JavaScript frameworks. — Micro Frontends
新建 Umi 工程
我们一共需要新建 3 个 Umi 工程:1 个主应用和 2 个子应用 app1、app2。
开发主应用
编写 Layout 代码
新建 src/layouts/index.tsx,编写代码如下:
import React from "react";
import { Link } from "umi";
const Layout: React.FC = ({ children }) => {
return (
<div>
<div>
<div>
<Link to="/app1">app1</Link>
</div>
<div>
<Link to="/app2">app2</Link>
</div>
</div>
{children}
</div>
);
};
export default Layout;
安装 @umijs/plugin-qiankun
yarn add @umijs/plugin-qiankun -D
配置主应用
在 .umirc.ts 中添加配置如下:
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'app1',
entry: 'http://localhost:8001',
},
{
name: 'app2',
entry: 'http://localhost:8002',
},
],
// 配置微应用关联的路由
routes: [
{
path: '/app1',
microApp: 'app1',
},
{
path: '/app2',
microApp: 'app2',
},
],
},
},
开发子应用
修改 index page 代码
为了方便区分 app1 和 app2,我们将 app1 和 app2 工程的 src/pages/index.tsx 分别修改为:
// app1/src/pages/index.tsx
import React from "react";
import styles from "./index.less";
const Index: React.VFC = () => {
return (
<div>
<h1 className={styles.title}>App1</h1>
</div>
);
};
export default Index;
// app2/src/pages/index.tsx
import React from "react";
import styles from "./index.less";
const Index: React.VFC = () => {
return (
<div>
<h1 className={styles.title}>App2</h1>
</div>
);
};
export default Index;
安装 @umijs/plugin-qiankun
yarn add @umijs/plugin-qiankun -D
配置子应用
给 .umirc.ts 增加 qiankun 配置即可:
qiankun: {
slave: {},
},
效果演示
第二节:MicroApp组件
装载子应用的两种方式
子应用的装载有两种方式,我们上一节讲了第一种方式,使用路由绑定的方式:
import { defineConfig } from 'umi';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
fastRefresh: {},
qiankun: {
master: {
// 注册子应用信息
apps: [
{
name: 'app1',
entry: 'http://localhost:8001',
},
{
name: 'app2',
entry: 'http://localhost:8002',
},
],
// 配置微应用关联的路由
routes: [
{
path: '/app1',
microApp: 'app1',
},
{
path: '/app2',
microApp: 'app2',
},
],
},
},
});
今天我们来讲解另一种子应用的装载方式,使用 组件的方式。
官方文档中建议使用第一种方式来引入自带路由的子应用。使用第二种方式来引入不带路由的子应用。否则使用第二种方式需要自行关注微应用依赖的路由跟当前浏览器 url 是否能正确匹配上,不然很容易出现微应用加载了,但是页面没有渲染出来的情况。
在实践中我们发现第二种方式有个很重要的优势,就是在父子应用通信中可以很方便地支持动态数据的传递(应用通信会在第 3 节介绍),所以我们的微前端应用的子应用装载使用的是第二种方式。
使用 <MicroApp />
组件装载子应用
修改 umi 配置文件:
import { defineConfig } from 'umi';
import { APP_LIST } from './src/constants';
export default defineConfig({
nodeModulesTransform: {
type: 'none',
},
fastRefresh: {},
qiankun: {
master: {
// 注册子应用信息
apps: APP_LIST,
},
},
});
其中 APP_LIST 的值为:
import { IApp } from '@/types';
export enum EAppPath {
app1 = '/qiankun-app1',
app2 = '/qiankun-app2',
}
export const APP_LIST: IApp[] = [
{
name: 'app1',
entry: 'http://localhost:8001',
path: EAppPath.app1,
},
{
name: 'app2',
entry: 'http://localhost:8002',
path: EAppPath.app2,
},
];
编写使用<MicroApp />
组件装载子应用的逻辑:
import React, { useMemo } from 'react';
import { Link, useLocation, MicroApp } from 'umi';
import { APP_LIST, EAppPath } from '@/constants';
const Layout: React.VFC = () => {
const location = useLocation();
const pathname = location.pathname;
const app = useMemo(() => {
const app = APP_LIST.find((item) => item.path === pathname);
return app;
}, [pathname]);
const hasApp = () => {
return Boolean(app?.name);
};
const appPathKeys = Object.keys(EAppPath) as (keyof typeof EAppPath)[];
return (
<div>
<div>
{appPathKeys.map((key) => {
const path = EAppPath[key];
return (
<div key={key}>
<Link to={path}>{path}</Link>
</div>
);
})}
</div>
{hasApp() && <MicroApp name={app?.name} />}
</div>
);
};
export default Layout;
这样,就完成子应用的装载啦。
微应用的 loading 动画与组件样式
你可以通过配置 autoSetLoading 的方式,开启微应用的 loading 动画。
<MicroApp name={app?.name} autoSetLoading />
默认情况下,当检测到你使用的是 antd 组件库时,loading 动画使用的是 antd Spin 组件。
如果你需要定制自己的 loading 动画,或者修改组件的样式,你可以这样处理:
<MicroApp
name={app?.name}
autoSetLoading
// 设置自定义 loading 动画
loader={(loading: boolean) => {
if (!loading) {
return null;
}
return <div>loading</div>;
}}
// 微应用容器 class
className="myContainer"
// wrapper class,仅开启 loading 动画时生效
wrapperClassName="myWrapper"
/>
第三节:父子应用通信
通信方式一:配合 useModel 使用(推荐)
需确保已安装
@umijs/plugin-model
或@umijs/preset-react
主应用透传数据方式一
如果你用的 MicroApp 组件模式消费微应用,那么数据传递的方式就跟普通的 react 组件通信是一样的,直接通过 props 传递即可:
function MyPage() {
const [name, setName] = useState(null);
return <MicroApp name={name} onNameChange={(newName) => setName(newName)} />;
}
主应用透传数据方式二
如果你用的 路由绑定式 消费微应用,那么你需要在 src/app.ts 里导出一个 useQiankunStateForSlave 函数,函数的返回值将作为 props 传递给微应用,如:
// src/app.ts
export function useQiankunStateForSlave() {
const [masterState, setMasterState] = useState({});
return {
masterState,
setMasterState,
};
}
子应用获取数据方式一
子应用中会自动生成一个全局 model,可以在任意组件中获取主应用透传的 props 的值。
import { useModel } from 'umi';
function MyPage() {
const masterProps = useModel('@@qiankunStateFromMaster');
return <div>{JSON.stringify(masterProps)}</div>;
}
子应用获取数据方式二
通过高阶组件 connectMaster 来获取主应用透传的 props
import { connectMaster } from 'umi';
function MyPage(props) {
return <div>{JSON.stringify(props)}</div>;
}
export default connectMaster(MyPage);
通信方式二:基于 props 传递
1.主应用中配置 apps 时以 props 将数据传递下去
// src/app.js
export const qiankun = fetch('/config').then((config) => {
return {
apps: [
{
name: 'app1',
entry: '//localhost:2222',
props: {
onClick: (event) => console.log(event),
name: 'xx',
age: 1,
},
},
],
};
});
2.子应用在生命周期钩子中获取 props 消费数据
// 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);
},
};