ant design pro 程式碼學習(二) ----- 路由資料分析
本章節包含路由資訊(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引數。
-
其次,判斷是否有redirect引數,若有則將呼叫window.history.replaceState無重新整理狀態下更改url為localhost:8080/#/,也即路由為’/’,並返回redirect引數;如沒有redirect引數,則返回路由資訊中第一個已認證的路由,其中routerData是以路徑(path)為key的路由集合
綜上所述,當路由path=’/’時,url中含有redirect引數時,重定向到redirect對應的路由元件;當不含redirect引數時,重定向到第一個已認證的路由元件。