「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」
qiankun 并不是单一个框架,它在 single-spa 基础上添加更多的功能。任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
乾坤整合的主要是基于当前主流前端框架 vue, react, agular 实现的系统,jquery 应用支持相地较弱(主要因为大多是多页应用)
本文采用 React + qiankun
1. create-react-app qiankun_learn
我们期望的效果是,点击上方的 Tab 切换到对应的子应用。
2. yarn add react-router-dom
改造 index.js,使用 HashRouter 对应用进行包裹。
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import MyRouter from './router';
import './index.css';
ReactDOM.render(
<HashRouter>
<MyRouter />
</HashRouter>,
document.getElementById('root')
);
3. MyRouter 编写
MyRouter 中,包含着页面的当中的路由情况。
我们使用 BasicLayout 对页面进行包裹。当路有切换时,展示不同的组件。
import React, { Fragment } from 'react';
import { Route, Switch } from 'react-router-dom';
import BasicLayout from '../pages/layout/index';
function MyRouter() {
const LayoutRoute = [
{
url: '/app',
component: BasicLayout
},
{
url: '/signpost',
component: BasicLayout
},
{
url: '/link',
component: BasicLayout
},
{
url: '/testBench',
component: BasicLayout
},
{
url: '/funnel',
component: BasicLayout
},
{
url: '/dataplatform',
component: BasicLayout
},
{
url: '/data',
component: BasicLayout
},
{
url: '/accept',
component: BasicLayout
},
{
url: '/rules',
component: BasicLayout
},
{
url: '/disposal',
component: BasicLayout
},
{
url: '/serviceScore',
component: BasicLayout
}
];
return (
<Fragment>
<Switch>
{LayoutRoute.map(val => {
const { url, component } = val;
return <Route key={url} path={url} component={component} />;
})}
</Switch>
</Fragment>
);
}
export default MyRouter;
4. BasicLayout
采用 antd 中比较常见的布局
import React, { Fragment, useEffect } from "react";
import { Layout, Menu } from 'antd';
import { Link } from 'react-router-dom';
import Header from './Header';
import Content from './Content';
const { Sider, Footer } = Layout;
function BasicLayout(props) {
const {
location: { pathname }
} = props;
return (
<Fragment>
<Layout>
<Header />
<div id="micro-app"></div>
{renderContent(pathname, props)}
<Footer style={{ textAlign: 'center' }}>Ant Design ©2018 Created by Ant UED</Footer>
</Layout>
</Fragment>
)
}
export default BasicLayout;
renderContent则是根据路由的不同,切换不同的内容。
const renderContent = (pathname, props) => {
if (pathname.includes('signpost')) {
return renderSignPostContent();
} else if (pathname.includes('governlink')) {
return renderGovernLinkContent(props);
} else if (pathname.includes('/strategy') {
return renderStrategyContent();
} else if (pathname.includes('dataplatform')) {
return renderDataPlatformContent(props);
} else if (pathname.includes('accept') || pathname.includes('disposal')) {
return null;
}
return (
<Content contentClass={'normal-wrap'}></Content>
)
}
Content 是个基础组件,在这个组件中,通过 shouldComponentUpdate 做一些优化。
src/pages/layout/Content.js
import React from "react";
import { Layout } from 'antd';
import RoutePaths from '../../router/paths';
import { Switch, Route } from "react-router-dom";
const { Content } = Layout
class MyContent extends React.Component {
shouldComponentUpdate(nextProps) {
if (this.props.contentClass !== nextProps.contentClass) {
return true
} else {
return false;
}
}
render() {
return (
<Content className={this.props.contentClass}>
<Switch>
{
RoutePaths.map(val => {
const { url, component } = val;
return <Route key={url} path={url} component={component}></Route>
})
}
</Switch>
</Content>
)
}
}
export default MyContent;
5. RoutePaths
当路由命中的时候,切换显示对应的组件。
import Error from '../pages/error/404/index'
import Noauth from '../pages/error/noauth/index'
import Home from '../pages/home/index'
import Signpost from '../pages/signpost/queryList';
import LinkConfig from '../pages/governLink/linkConfig';
import Funnel from '../pages/funnel/index';
const ROUTES = [
{
url: '/app/404',
component: Error,
title: '异常-页面不存在'
},
{
url: '/app/noauth',
component: Noauth,
title: '异常-无访问权限'
},
/**
* 欢迎页
*/
{
url: '/home',
component: Home,
title: '首页'
},
/*
* 路标
*/
{
url: '/signpost/signpost_query_list',
component: Signpost,
title: '列表'
},
/*
* 配置
*/
{
url: '/governlink/linkConfig',
component: LinkConfig,
title: '链接'
},
{
url: '/juanzong/query_list',
component: Funnel,
title: "1"
}
];
export default ROUTES;
6. 每个子应用的内容
const renderSignPostContent = () => {
return (
<Layout>
<Sider
className="root-sider signpost-sider"
width={200}
>
{renderSiderMenu(signpostMenu)}
</Sider>
<Content contentClass={'signpost-wrap'}></Content>
</Layout>
)
}
renderSiderMenu 是抽离出来的单独组件,传入内容,渲染出不同的 Sider。
const renderSiderMenu = (menu) => {
return (
<Menu
theme="light"
mode="inline"
>
{menu.map(item => {
const { url, name, key, children } = item;
if (!Array.isArray(children) || !children.length) {
return (
<MenuItem>
<Link to={url}>{name}</Link>
</MenuItem>
);
}
return (
<SubMenu
key={key}
title={
<Fragment>
<span>{name}</span>
</Fragment>
}
>
{children.map(item => {
return (
<MenuItem key={item.url}>
<Link to={item.url}>{item.name}</Link>
</MenuItem>
);
})}
</SubMenu>
);
})}
</Menu>
);
}
写到目前,我们可以看到页面的显示
7. 🔨 使用 qiankun
src/pages/layout/index.js
useEffect(() => {
const { host } = window.location;
registerMicroApps([
{
name: 'rules',
entry: host.startsWith('****')
? `****`
: host === '*****'
? `*****`
: '****',
container: '#micro-app',
activeRule: () => window.location.hash.startsWith('#/rules')
},
{
name: 'accept',
entry: host.startsWith('****')
? `******`
: host === '****'
? `*****`
: '*****',
container: '#micro-app',
activeRule: () => window.location.hash.startsWith('#/accept')
},
{
name: 'strategy',
entry: host.startsWith('****')
? `*****`
: host === 'localhost:3000'
? '****'
: host === '*****'
? `*****`
: '*****',
container: '#micro-app',
activeRule: () => window.location.hash.startsWith('#/strategy')
}
]);
start();
})