自前端框架風靡以來,路由一詞在前端的熱度與日俱增,他是幾乎全部前端框架的核心功能點。不一樣於後端,前端的路由每每須要表達更多的業務功能,例如與菜單耦合、與標題耦合、與「麪包屑」耦合等等,所以不多有拆箱即用的完整方案,多多少少得二次加工一下。前端
優秀的框架能夠縮短 90% 以上的無效開發時間,螞蟻的 UmiJS 是我見過最優雅的 React 應用框架,或者能夠直接說是最優雅的前端解決方案(歡迎挑戰),本系列將逐步展開在其之上的應用,本文重點爲「路由」,其他部分後續系列繼續深刻。react
動碼以前先構想下本次咱們要實現哪些功能:後端
上述每一點的功能都不復雜,若不追求極致,其實默認的約定式路由基本可以知足需求(詳情查詢官方文檔,此處不作展開)。前端框架
先從菜單出發,如下應當是一個最簡潔的目錄結構:antd
const menu = [ { name: '父節點', path: 'parent', children: [{ name: '子頁面', path: 'child' }] } ];
使用遞歸補齊 child 路徑:架構
const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; const formatMenu = (data, parentPath = `${define.BASE_PATH}/`) => { return data.map((item) => { let { path } = item; if (!reg.test(path)) { path = parentPath + item.path; } const result = { ...item, path }; if (item.children) { result.children = formatMenu(item.children, `${parentPath}${item.path}/`); } return result; }); }
菜單的子節點纔是真正的頁面,因此若當前路徑是父節點,咱們指望的是可以自動跳轉到父節點寫的第一個或者特定的頁面:框架
const redirectData = []; const formatRedirect = item => { if (item && item.children) { if (item.children[0] && item.children[0].path) { redirectData.push({ path: `${item.path}`, redirect: `${item.children[0].path}` }); item.children.forEach(children => { formatRedirect(children); }); } } }; const getRedirectData = (menuData) => { menuData.forEach(formatRedirect); return redirectData };
然後即是將自動跳轉的路徑組裝入路由節點:ide
const routes = [ ...redirect, { path: define.BASE_PATH, component: '../layouts/BasicLayout', routes: [ { path: `${define.BASE_PATH}/parent`, routes: [ { title: '子頁面', path: 'child', component: './parent/child', } ], }, { component: './404', } ] } ];
路由配置最後須要注入配置文件 .umirc.js:this
import { plugins } from './config/plugins'; import { routes } from './config/routes'; export default { plugins, routes }
import { Layout } from 'antd'; import React, { PureComponent, Fragment } from 'react'; import { ContainerQuery } from 'react-container-query'; import DocumentTitle from 'react-document-title'; import { query } from '@/utils/layout'; import Footer from './Footer'; import Context from './MenuContext'; const { Content } = Layout; class BasicLayout extends PureComponent { render() { const { children, location: { pathname } } = this.props; const layout = ( <Layout> <Layout> <Content> {children} </Content> <Footer /> </Layout> </Layout> ); return ( <Fragment> <DocumentTitle title={this.getPageTitle(pathname)}> <ContainerQuery query={query}> {params => ( <Context.Provider> {layout} </Context.Provider> )} </ContainerQuery> </DocumentTitle> </Fragment> ); } } export default BasicLayout;
結合路由與菜單獲取麪包屑:spa
getBreadcrumbNameMap() { const routerMap = {}; let path = this.props.location.pathname; if (path.endsWith('/')) { path = path.slice(0, path.length - 1); } const mergeRoute = (path) => { if (path.lastIndexOf('/') > 0) { const title = this.getPageTitle(path); if (title) { routerMap[path] = { name: title, path: path }; } mergeRoute(path.slice(0, path.lastIndexOf('/'))); } }; const mergeMenu = data => { data.forEach(menuItem => { if (menuItem.children) { mergeMenu(menuItem.children); } routerMap[menuItem.path] = { isMenu: true, ...menuItem }; }); }; mergeRoute(path); mergeMenu(this.state.menuData); return routerMap; }
從路由中獲取 PageTitle:
getPageTitle = (path) => { if (path.endsWith('/')) { path = path.slice(0, path.length - 1); } let title; this.props.route.routes[0].routes.forEach(route => { if (route.path === path) { title = route.title; return; } }) return title; };
此篇隨筆比較混亂,寫做脈絡不對,仍是應該簡述下在 umijs 之上的架構設計,再往下深刻探討應用點,缺的部分會在後續系列中補上~
個人公衆號《捷義》