1、getSelectedMenuKeys 获取选中的菜单的key
getSelectedMenuKeys = () => {
const {location: {pathname}} = this.props;
return getMeunMatchKeys(this.flatMenuKeys, urlToList(pathname));
};
getSelectedMenuKeys中涉及到getMeunMatchKeys()、this.flatMenuKeys、urlToList(),下边分别对其进行分析。
this.flatMenuKeys = getFlatMenuKeys(props.menuData);
.....
export const getFlatMenuKeys = menu =>
menu.reduce((keys, item) => {
keys.push(item.path);
if (item.children) {
return keys.concat(getFlatMenuKeys(item.children));
}
return keys;
}, []);
getFlatMenuKeys通过对props.menuData数据递归调用,返回props.menuData中所有层级数据的path值(Array类型),其中path为完整的路径值(getMenuData()已处理过)
export function urlToList(url) {
//filter(i => i) 去除空
const urllist = url.split('/').filter(i => i);
return urllist.map((urlItem, index) => {
return `/${urllist.slice(0, index + 1).join('/')}`;
});
}
将pathname传入到urlToList方法中,urlToList首先对url用’/’分割,filter方法对空值进行处理(分割后第一个元素为空)。返回一级路径、二级路径…依次类推。例如当前pathname为/A/B时,则返回[‘/A’,’/A/B’]
export const getMeunMatchKeys = (flatMenuKeys, paths) =>
paths.reduce((matchKeys, path) => (
matchKeys.concat(
flatMenuKeys.filter(item => pathToRegexp(item).test(path))
)), []);
getMeunMatchKeys对flatMenuKeys、paths做双重循环,其中pathToRegexp会对/path/:id等数据做处理,会匹配/path路径。返回值为,flatMenuKeys中包含的paths元素(返回类型Array)。
综上所述,getFlatMenuKeys返回当前路由的pathname对应在flatMenuKeys中匹配项。例如当前pathname=’/dashboard/analysis’(有效路由),则返回[‘/dashboard’,’/dashboard/analysis’];当前pathname=’/aaa’(无效路由),则返回[],因为flatMenuKeys找不到匹配项。
2、handleOpenChange 切换菜单时,更改选中的菜单key
isMainMenu = key => {
return this.menus.some(item => key && (item.key === key || item.path === key));
};
handleOpenChange = openKeys => {
const lastOpenKey = openKeys[openKeys.length - 1];
const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1;
this.setState({
openKeys: moreThanOne ? [lastOpenKey] : [...openKeys],
});
};
......
//Menu组件中onOpenChange事件
onOpenChange={this.handleOpenChange}
isMainMenu用来检测当前路径是否为一级菜单路径。
由ant design中的Menu组件可知,onOpenChange事件的回调方法中,传入参数openKeys为当前多级路径、将被打开的路径的集合。
如果当前路径、即将打开的路径是一级菜单间切换,则moreThanOne必为true,例如当前’/dashboard/analysis’,即将打开’/list’,此时openKeys为[‘/dashboard’,’/dashboard/analysis’,’/list’],则openKeys为即将打开的页面的路由,即/list’;
如果是当前菜单的子菜单打开,则moreThanOne为false,例如当前打开的菜单时’/list’(列表项),即将打开’/list/search’(搜索列表),则openKeys为[“/list”, “/list/search”],此时Menu组件会打开:列表-搜索列表;
如果,点击当前打开的菜单,则此时即为关闭菜单,openKeys为[]。
*注:结合getSelectedMenuKeys、handleOpenChange。对selectedKeys做了如下处理:如果根据当前路由信息找不到匹配的菜单项,则当前选中的菜单项为解决要打开的菜单项。*
let selectedKeys = this.getSelectedMenuKeys();
if (!selectedKeys.length) {
selectedKeys = [openKeys[openKeys.length - 1]];
}
3、getNavMenuItems 、getSubMenuOrItem获取菜单项入口方法
getNavMenuItems获取存在菜单名称(name)、且可见的菜单(!item.hideInMenu)。其中getSubMenuOrItem根据当前的是否存在子菜单返回或者
getNavMenuItems = menusData => {
if (!menusData) {
return [];
}
return menusData
.filter(item => item.name && !item.hideInMenu)
.map(item => {
// make dom
const ItemDom = this.getSubMenuOrItem(item);
return this.checkPermissionItem(item.authority, ItemDom);
})
.filter(item => item);
};
getSubMenuOrItem = item => {
if (item.children && item.children.some(child => child.name)) {
const childrenItems = this.getNavMenuItems(item.children);
if (childrenItems && childrenItems.length > 0) {
return (
<SubMenu
title={
item.icon ? (
<span>
{getIcon(item.icon)}
<span>{item.name}</span>
</span>
) : (
item.name
)
}
key={item.path}
>
{childrenItems}
</SubMenu>
);
}
return null;
} else {
return <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>;
}
};
第一次调用getSubMenuOrItem方法,
<Menu>
......
{this.getNavMenuItems(this.menus)}
</Menu>
有如下逻辑,遍及菜单数据(一级菜单):
1. 如果当前菜单数据(item),不存在子菜单或者子菜单中不存在name属性时,则判断为当前菜单项没有子菜单,返回Menu.Item;此时代表一级菜单没有子菜单,则返回的Menu.Item直接作为
- 如果当前菜单数据存在子菜单,则对子菜单调用getNavMenuItems方法,此处存在递归调用getSubMenuOrItem。继续执行1、2。当前子菜单如果没有下一级子菜单,则返回SubMenu - Menu.Item - SubMenu,如果存在继续递归调用。此时一级菜单已经确定含有子菜单,所以递归的结果是作为SubMenu的子元素,当然SubMenu中也有可能包含SubMenu。
4、checkPermissionItem 获取当前菜单的权限
- 方法使用:
const ItemDom = this.getSubMenuOrItem(item);
return this.checkPermissionItem(item.authority, ItemDom);
- 方法引用
checkPermissionItem = (authority, ItemDom) => {
if (this.props.Authorized && this.props.Authorized.check) {
const {check} = this.props.Authorized;
return check(authority, ItemDom);
}
return ItemDom;
};
由getSubMenuOrItem返回的是SubMenu或者Menu.Item组件,checkPermissionItem方法中的Authorized属性是组件,由ant design pro 代码学习(一) —– 路由分析分析可知,this.props.Authorized.check() 接受三个参数:1、组件的权限值;2、鉴权通过返回的组件;3、鉴权不通过返回的组件。方法内部会根据当前权限值authority与currentAuthority对比,决定返回值。
由menuData中的数据可知,当前菜单数据中,除了账户(user)的authority为’guest’,其他均为undefined。而当前的权限值currentAuthority是根据调用接口’/api/login/account’来确定的(具体在后续登录部分在分析)登录后不为’guest’(具体根据账号类型为admin或者user)。
check()中当传入的authority为undefined时,则直接认为是鉴权通过。由于账户(user)相关的权限为guest,不等于’admin’,此时返回undefined(因为鉴权未通过的组件参数未传入)。getNavMenuItems()方法中通过Array.filter过滤掉undefined、null、‘’ 等元素。否则的话根据react官方文档api可知,在jsx中undefined会被渲染为空
false,null,undefined,和 true 都是有效的的 children(子元素) 。但是并不会被渲染。 —– react文档-深入 JSX
5、getMenuItemPath 生成菜单项
根据菜单数据中的path属性,判断是否含有https,如果有,则认为是链接,生成a标签,如果没有则认为系统内部页面,则生成Link标签。getIcon用于生成菜单图标 。
getMenuItemPath = item => {
const itemPath = this.conversionPath(item.path);
const icon = getIcon(item.icon);
const {target, name} = item;
// Is it a http link
if (/^https?:\/\//.test(itemPath)) {
return (
<a href={itemPath} target={target}>
{icon}
<span>{name}</span>
</a>
);
}
return (
<Link
to={itemPath}
target={target}
replace={itemPath === this.props.location.pathname}
onClick={
this.props.isMobile
? () => {
this.props.onCollapse(true);
}
: undefined
}
>
{icon}
<span>{name}</span>
</Link>
);
};
6、菜单数据流程图
上一篇:ant design pro 代码学习(二) —– 路由数据分析
下一篇:ant design pro 代码学习(四) —– 数据mock