微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,主应用和微应用技术栈无关,全部具备独立开发、独立部署、独立运行的能力。
在开始学习之前,首先要知道 qiankun 和 plugin-qiankun 不是一个东西。前者是纯净版,后者是基于前者的封装,是 umi 脚手架下安装 qiankun 插件,两种是不同的接入方式。
一. qiankun
1. 安装
yarn add qiankun // 或者 npm i qiankun -S
2. 主应用配置(注册子应用)
方法一:约定式路由,在主应用入口文件( app.js 或 index.js )中增加以下代码:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // 子应用package.json中的name值
entry: '//localhost:7100', // 子应用访问地址
container: '#yourContainer', // 主应用容器(子应用会插入该位置)
activeRule: '/yourActiveRule', // 匹配子应用路由
props: {}, // 向子应用传递props,子应用在生命周期函数中获取
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start(); // 启动注册微应用
方法二:手动加载微应用,适用于在点击事件中加载子应用的场景。
import { loadMicroApp } from 'qiankun';
loadMicroApp({
name: 'app',
entry: '//localhost:7100',
container: '#yourContainer',
});
3. 子应用
子应用不需要额外安装任何其他依赖即可接入 qiankun 主应用,只需要在自己的入口 js 中导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
二. @umijs/plugin-qiankun
1. 安装
yarn add @umijs/plugin-qiankun -D // 主应用和子应用都需要安装
2. 主应用配置
第一步:注册子应用
方法一:插件构建期配置子应用(约定式路由:config.js)
export default {
qiankun: {
master: { // MasterOptions
// 注册子应用信息
apps: [
{
name: 'app1', // 唯一 id,子应用package.json中的name值
entry: '//localhost:7001', // html entry
},
{
name: 'app2', // 唯一 id
entry: '//localhost:7002', // html entry
},
],
},
},
};
方法二:运行时动态配置子应用(src/app.js)
// 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
export const qiankun = fetch('/config').then(({ apps }) => ({ // MasterOptions
apps, // 注册子应用信息
routes, // 子应用运行时需要注册的微应用路由,和 .umirc.js中的routes效果一样
lifeCycles: { // 生命周期钩子
afterMount: (props) => {
console.log(props);
},
},
}));
第二步:装载子应用
方法一:路由配置(.umirc.js 或 config/routes.js)
export default {
routes: [
{
path: '/',
component: '../layouts/index.js',
routes: [
{
path: '/app1/user',
microApp: 'app1',
microAppProps: {
base: '', // 路由传空可以直接跳转到对应路由 /user,不会重定向到app1首页
params: '', // 向子应用传参,子应用在生命周期函数中取值
}
},
{
path: '/app2', // 配置 app2 关联的路由
microApp: 'app2',
settings: {
singular: false, // 可以在主应用中多次加载同一个微应用,每个实例都是独立的,具有自己的状态、路由等信息。
},
microAppProps: { // 配置微应用的一些常用属性
autoSetLoading: true, // 微应用启动时显示加载状态
className: 'appClassName', // 设置微应用的根元素 CSS 类名
wrapperClassName: 'wrapperClass', // 设置微应用容器的 CSS 类名。
},
},
{
path: '/',
component: './index.js',
},
],
},
],
}
方法二:使用 <MicroApp /> 组件
import { MicroApp } from 'umi';
export function MyPage() {
return (
<MicroApp name="app1" />
)
}
方法三:使用 <MicroAppWithMemoHistory /> 组件
import { MicroAppWithMemoHistory } from 'umi';
export function MyPage() {
// 和<MicroApp />相比,<MicroAppWithMemoHistory />引入的app2在切换菜单时,主应用路由不会变化
return (
<MicroAppWithMemoHistory name="app2" url="/user" current={2} pageSize={5} />
)
}
主应用路由 /home,使用 <MicroApp /> 时,路由跳转到 /app2/user,使用 <MicroAppWithMemoHistory /> ,主应用路由不变。
3. 子应用配置
插件注册(config.js)
export default {
qiankun: {
slave: {},
},
};
生命周期钩子(src/app.js,可选)
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);
},
};
4. 父子应用通讯
方法一:配合 useModel 使用
- 主应用
-
如果使用 <MicroApp /> 组件模式消费微应用,那么数据传递的方式就跟普通的 react 组件通信是一样的,直接通过 props 传递。
import React, { useState } from 'react'; import { MicroApp } from 'umi'; export default function () { const [name, setName] = useState('Hello'); return ( <MicroApp name="app1" name={name}/> ) }
-
如果使用绑定式路由消费微应用,需要在 src/app.js 里导出一个 useQiankunStateForSlave 函数,函数的返回值将作为 props 传递给微应用
import React, { useState } from 'react'; import { useModel } from 'umi'; export default function () { // 主应用使用 useModel('@@qiankunStateForSlave') 获取子应用传递的全局状态 const { setQiankunGlobalState } = useModel('@@qiankunStateForSlave'); } // app.js // 配合 useModel('@@qiankunStateForSlave') 实现父子应用通讯 export const useQiankunStateForSlave = () => { const [globalState, setQiankunGlobalState] = useState({ slogan: 'Hello MicroFrontend', }); return { globalState, setQiankunGlobalState, }; };
-
- 子应用
- 使用 useModel
import { useModel } from 'umi'; export default function () { // 微应用中使用 useModel('@@qiankunStateFromMaster') 获取主应用传递过来的全局状态 const { name, globalState } = useModel('@@qiankunStateFromMaster') || {}; return ( <div> <p>name: {name}</p> <p>globalState: {JSON.stringify(globalState)}</p> </div> ) }
- 使用高阶组件 connectMaster
import { connectMaster } from 'umi'; function MyPage(props) { return <div>{JSON.stringify(props)}</div>; } export default connectMaster(MyPage);
- 使用 useModel
方法二:基于props传递
主应用配置 apps 时以 props 的方式传递,子应用在生命周期钩子中获取 props 消费数据。
// src/app.js 对应动态加载路由
export const qiankun = fetch('/config').then((config) => {
return {
apps: [
{
name: 'app1',
entry: '//localhost:7001',
props: {
onClick: (event) => console.log(event),
name: 'xx',
age: 1,
},
},
],
};
});