1、基本概述
在项目开发过程中,通常的企业后台系统会包含头部(Header)、侧边栏(Sider),主体内容(Content)以及底部(Footer)。通常情况下我们会在Layout文件中布局,并对头部、侧边栏、底部封装成对应的组件,而针对主体内容部分,通过路由来控制显示不同的内容。
但是也存在一些页面不需要按照上边的布局方式来展示,例如登录、注册,或者其他类似的页面。
针对上述情况,通常情况下,我们会封装多个全局的Layout,根据对路由的判断,来确定具体的加载哪一个Layout。
在Umi.js的API中 https://umijs.org/zh/guide/router.html#%E5%85%A8%E5%B1%80-layout ,我们可以找到下边的答案:
约定 src/layouts/index.js 为全局路由,返回一个 React 组件,通过 props.children 渲染子组件。
export default function(props) { return ( <> <Header /> { props.children } <Footer /> </> ); }
你可能需要针对不同路由输出不同的全局 layout,umi 不支持这样的配置,但你仍可以在 layouts/index.js 对 location.path 做区分,渲染不同的 layout 。比如想要针对 /login 输出简单布局,
export default function(props) { if (props.location.pathname === '/login') { return <SimpleLayout>{ props.children }</SimpleLayout> } return ( <> <Header /> { props.children } <Footer /> </> ); }
全局的Layout需要放置在根目录下Layouts文件夹下(index文件),全局Layout可以理解为一级路由。
2、核心代码分析
Umi.js的文档中提供的方法原理上是对的,但是也存在一些问题。如果不同的全局Layout之间存在一些复杂的业务处理,或者需要在对应的生命周期中处理一些方法时,就会存在功能职责不单一,不利于后期的维护。针对上述问题,做如下改进:
import React from 'react';
import {connect} from 'dva';
import BasicLayout from "./BasicLayout";
import UserLayout from "./UserLayout";
import {userLayoutConfig} from "./config";
class Index extends React.Component {
render() {
const {location} = this.props,
{pathname} = location;
if (userLayoutConfig && userLayoutConfig.includes(pathname)) {
return <UserLayout {...this.props} />;
}
return (
<BasicLayout {...this.props} />
);
}
}
export default connect(({global}) => ({global}))(Index);
其中BasicLayout、UserLayout为不同场景下的全局Layout,userLayoutConfig变量中配置需要渲染UserLayout的场景,通过改造之后,实现了BasicLayout、UserLayout的分离,在各自的组件内可以根据不同的场景进行布局,也可以维护各自的生命周期,以及组件内的逻辑(包含头部信息、鉴权、退出等等),具体的实现可以自行根据各自的业务进行,可以参考BasicLayout、UserLayout中的写法。
以BasicLayout为例,做简要说明,核心代码如下:
import styles from './index.less';
import React from 'react';
import {LocaleProvider, Layout} from 'antd';
import zhCN from 'antd/lib/locale-provider/zh_CN';
import router from 'umi/router';
import {MyHeader, MySider} from '../components';
const {Header, Sider, Content} = Layout;
/*页面布局,可以理解为一级路由*/
export default class BasicLayout extends React.Component {
componentDidMount() {
const {dispatch} = this.props;
/*查询用户信息*/
dispatch({
type: 'global/queryUserInfo',
})
/*查询菜单数据*/
dispatch({
type: 'global/queryMenuList'
});
}
render() {
const {global, children, dispatch} = this.props;
const {userInfo, menuList} = global;
const headerProps = {
userInfo,
onExit: () => {
dispatch({
type: 'global/exit',
}).then(() => {
router.push('/login');
});
},
};
const siderProps = {
menuList,
};
const MyFooter = () => (
<div className={styles.footerContent}>Copyright © 2018产品技术部出品</div>
);
return (
<LocaleProvider locale={zhCN}>
<Layout className={styles.normal}>
<Header>
<MyHeader {...headerProps} />
</Header>
<Layout>
<Sider width="170">
<MySider {...siderProps} />
</Sider>
<Content className={styles.contentWrap}>
<div className={styles.content}>{children}</div>
<MyFooter/>
</Content>
</Layout>
</Layout>
</LocaleProvider>
);
}
}
BasicLayout通过Header、Sider、Content的组件进行布局,并通过LocaleProvider设置国际化;在生命周期componentDidMount中获取用户信息(queryUserInfo)以及获取动态菜单的数据(queryMenuList),关于动态菜单的渲染,在下一章节中,会做详细说明。如果涉及的其他的具体业务,可以自行调整。
上一篇:基于Umi搭建的个人Dva脚手架(二) - HTTP请求的封装
下一篇:基于Umi搭建的个人Dva脚手架(四) - 可配置的表单组件封装