本章节包含路由信息(common/router)、侧边栏菜单信息(common/menu)、基本路由(一级路由)UserLayout组件,BasicLayout组件、以及侧边栏SiderMenu组件中对数据的处理。主要涉及到以下几个方法,分别逐个分析其功能。
备注:本文中代码部分只截取涉及到的相关代码,完整代码请查看ant design Pro官方代码。
1、getMenuData:获取菜单配置数据
对common/menu.js中定义的menuData数据做处理,如果path不是含有http的链接,即将path设置为完整的相对路径(即父菜单的路径 + 子菜单的路径),并获取当前菜单的权限,如果没有权限值,则继承父菜单的权限。其他相关属性(name、hideInMenu等)保持不变。
function formatter(data, parentPath = '/', parentAuthority) {
return data.map(item => {
let { path } = item;
if (!isUrl(path)) {
path = parentPath + item.path;
}
const result = {
...item,
path,
authority: item.authority || parentAuthority,
};
if (item.children) {
result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
}
return result;
});
}
export const getMenuData = () => formatter(menuData);
2、getFlatMenuData
由1可知,menuData经过getMenuData()处理之后,path已经变成了完整的相对路径,getFlatMenuData主要是对getMenuData()的数据做处理。有如下使用方式,
getFlatMenuData有两个作用:
1. 将getMenuData()的结果(Array)转换成以path为key的对象数据(Object);
2. 通过递归调用,将getMenuData()的结果的父子层级结构的数据处理为平行数据。
- 方法引用:
const menuData = getFlatMenuData(getMenuData());
- 方法定义:
function getFlatMenuData(menus) {
let keys = {};
menus.forEach(item => {
if (item.children) {
keys[item.path] = { ...item };
keys = { ...keys, ...getFlatMenuData(item.children) };
} else {
keys[item.path] = { ...item };
}
});
return keys;
}
3、 getRouterData:获取路由数据
综合1、2可知,处理后的menuData是以path为key的Object,其中value为对应的菜单项配置。其中routerConfig为项目的路由配置,具体参看源代码。
循环遍历routerConfig:
1. 当路由的path能在menuData中找到匹配(即菜单项对应的路由),则获取菜单项中当前path的配置menuItem;
2. 获取当前path的路由配置router;
3. 返回最新路由信息,name、authority、hideInBreadcrumb三个属性如果router中没有配置,则取菜单项中的配置。
export const getRouterData = app => {
const routerConfig = {...};
const menuData = getFlatMenuData(getMenuData());
const routerData = {};
Object.keys(routerConfig).forEach(path => {
const pathRegexp = pathToRegexp(path);
const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
let menuItem = {};
if (menuKey) {
menuItem = menuData[menuKey];
}
let router = routerConfig[path];
router = {
...router,
name: router.name || menuItem.name,
authority: router.authority || menuItem.authority,
hideInBreadcrumb: router.hideInBreadcrumb || menuItem.hideInBreadcrumb,
};
routerData[path] = router;
});
return routerData;
};
4、getRoutes:获取一级路由对应的路由数据
用于获取当前路径对应的路由数据。共有几处使用该方法:
组件名称 | match.path | 父组件 |
---|---|---|
BasicLayout | / | – |
UserLayout | /user | – |
StepForm | /form/step-form | BasicLayout |
SearchList | /list/search | BasicLayout |
- 方法使用
{getRoutes(match.path, routerData).map(item => (...))}
首先获取所有以path开头且路由不等于path的路由数据routes。getRenderArr(routes)对路由数据进行操作,过滤掉包含关系的路由。
例如:当path=’/’时,’/list/search’、’/list/search/projects’、’/list/search/applications’、 ‘/list/search/articles’。这个四个路由信息存在包含关系。getRenderArr方法会将后三个过滤掉。渲染路由时,只生成’/list/search’对应的组件,也即SearchList。同时在SearchList中,调用getRoutes时,传入path=’ /list/search’,会从routerData筛选出’/list/search/projects’、’/list/search/applications’、 ‘/list/search/articles’这个三个路由的数据,并生成对应的Route组件。==此处/list/search可以理解为生成一个三级路由。==
- 方法定义
export function getRoutes(path, routerData) {
let routes = Object.keys(routerData).filter(
routePath => routePath.indexOf(path) === 0 && routePath !== path
);
routes = routes.map(item => item.replace(path, ''));
const renderArr = getRenderArr(routes);
const renderRoutes = renderArr.map(item => {
const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
return {
exact,
...routerData[`${path}${item}`],
key: `${path}${item}`,
path: `${path}${item}`,
};
});
return renderRoutes==;==
}
该处源码存在两个小问题:
1. 当path=’/’时,当前进入了BasicLayout组件,getRoutes会返回含有/user的路由信息,会生成一个无效的路由?因为当路由为/user时,在一级路由的时候,已经被匹配了,就已经进入UserLayout,所以不可能在BasicLayout中出现匹配/user;
2. 过滤路由包含关系时,依赖其他routerConfig中出现的顺序,如上例,若调换’/list/search’、’/list/search/projects’在routerConfig中的顺序,则会出现404错误。因为此时/list/search/projects中不存在生成三级路由的代码。因此无法匹配路由,会出现404错误。此处需要注意路由配置的位置关系;
5、 getPageTitle:获取当前url对应的页面title
此方法在UserLayout、BasicLayout组件中均有使用,通过获取路由信息中的name属性,动态的修改页面的title显示。其中DocumentTitle引用’react-document-title’。
- 方法引用:
<DocumentTitle title={this.getPageTitle()}>
...
</DocumentTitle>
- 方法定义:
getPageTitle() {
const { routerData, location } = this.props;
const { pathname } = location;
let title = 'Ant Design Pro';
if (routerData[pathname] && routerData[pathname].name) {
title = `${routerData[pathname].name} - Ant Design Pro`;
}
return title;
}
6、 getBashRedirect:BasicLayout下路由重定向
此方法在BasicLayout组件中使用,用来设置路由为’/’时,重定向的路径。
方法引用:
// BasicLayout.js const bashRedirect = this.getBashRedirect(); ... <Content style={{ margin: '24px 24px 0', height: '100%' }}> ... <Redirect exact from="/" to={bashRedirect} /> <Route render={NotFound} /> </Switch> </Content>
方法定义:
// BasicLayout.js getBashRedirect = () => { const urlParams = new URL(window.location.href); const redirect = urlParams.searchParams.get('redirect'); // Remove the parameters in the url if (redirect) { urlParams.searchParams.delete('redirect'); window.history.replaceState(null, 'redirect', urlParams.href); } else { const { routerData } = this.props; // get the first authorized route path in routerData const authorizedPath = Object.keys(routerData).find( item => check(routerData[item].authority, item) && item !== '/' ); return authorizedPath; } return redirect; };
首先,获取url中redirect参数,这里的参数指的是url中search参数,而并非hash参数。如下例所以,第一个url能即为search参数,第二个url即为hash参数。
- http://localhost:8000/?redirect=/result/success
http://localhost:8000/#/?redirect=result/success
其次,判断是否有redirect参数,若有则将调用window.history.replaceState无刷新状态下更改url为localhost:8080/#/,也即路由为’/’,并返回redirect参数;如没有redirect参数,则返回路由信息中第一个已认证的路由,其中routerData是以路径(path)为key的路由集合
综上所述,当路由path=’/’时,url中含有redirect参数时,重定向到redirect对应的路由组件;当不含redirect参数时,重定向到第一个已认证的路由组件。
7、 路由数据流程图
上一篇:ant design pro 代码学习(一) —– 路由分析
下一篇:ant design pro 代码学习(三) —– 菜单数据分析