antdesignpro(三)路由和菜单
⼀、概述
⼆、原⽂摘要
  路由和菜单是组织起⼀个应⽤的关键⾻架,我们的脚⼿架提供了⼀些基本的⼯具及模板,帮助你更⽅便的搭建⾃⼰的路由/菜单。
  如果你想了解更多关于browserHistory和hashHistory,请参看。
注意:我们的脚⼿架依赖dva@2,路由⽅⾯是基于react-router@4的实现,在写法以及 API 上与之前的版本有较⼤不同,所以,在开始前你需要具备⼀些相关的基础知识。这⾥给出⼏篇关键⽂档:
2.1、基本结构
脚⼿架通过结合⼀些配置⽂件、基本算法及⼯具函数,搭建好了路由和菜单的基本框架,主要涉及以下⼏个模块/功能:路由⽣成结合路由信息的配置⽂件和预设的基本算法,提供了在不同层级⽂件中⾃动⽣成路由列表的能⼒。
菜单⽣成由确定数据结构的菜单配置⽂件⽣成,其中的菜单项名称,嵌套路径与路由有⼀定关联关系。
⾯包屑组件中内置的⾯包屑也可由脚⼿架提供的配置信息⾃动⽣成。
简单介绍下各个模块的基本思路。
2.1.1、路由
⽬前在脚⼿架中,除了,其余路由列表都是⾃动⽣成,其中最关键的就是中⼼化配置⽂件src/common/router.js,它的主要作⽤有两个:配置路由相关信息。如果只考虑⽣成路由,你只需要指定每条配置的路径及对应渲染组件。
输出路由数据,并将路由数据(routerData)挂载到每条路由对应的组件上。
这样我们得到⼀个基本的路由信息对象,它的结构⼤致是这样:
{
'/dashboard/analysis': {
component: DynamicComponent(),
name: '分析页',
},
'/dashboard/monitor': {
component: DynamicComponent(),
name: '监控页',
},
'/dashboard/workplace': {
component: DynamicComponent(),
name: '⼯作台',
},
}
为了帮助⾃动⽣成路由,在src/utils/utils.js中提供了⼯具函数 getRoutes,它接收两个参数:当前路由的路径及路由信息 routerData,主要完成两个⼯作:
筛选路由信息,筛选的算法为只保留当前 match.path 下最邻近的路由层级(更深⼊的层级留到嵌套路由中⾃⾏渲染),举个例⼦(每条为⼀个 route path):
// 当前 match.path 为 /
/a                // 没有更近的层级,保留
/a/b              // 存在更近层级 /a,去掉
/c/d              // 没有更近的层级,保留
/c/e              // 没有更近的层级,保留
/c/e/f            // 存在更近层级 /c/e,去掉
⾃动分析路由参数,除了下⾯还有嵌套路由的路径,其余路径默认设为 exact。
经过 getRoutes 处理之后的路由数据就可直接⽤于⽣成路由列表:
// src/layouts/BasicLayout.js
getRoutes(match.path, routerData).map(item => (
<Route
key={item.key}
path={item.path}
component={itemponent}
exact={act}
/>
))
注意:如果你不需要⾃动⽣成路由,也可以⽤ routerData ⾃⾏处理。
2.1.2、菜单
菜单信息配置在src/common/menu.js中,它的作⽤是:
配置菜单相关数据,菜单项的跳转链接为配置项及其所有⽗级配置 path 参数的拼接。
为src/common/router.js提供路由名称(name)等数据,根据拼接好的跳转链接来匹配相关路由。
如果你的项⽬并不需要菜单,你也可以直接在src/common/router.js中配置 name 信息。
配置⽂件输出的菜单数据,可以直接提供给侧边栏组件使⽤。。除了⽣成菜单,菜单数据还可辅助⽣成重定向路由等模块,参考。
2.1.3、⾯包屑
  之前提到的路由信息 routerData 可以直接传递给 PageHeader 组件⽤以⽣成⾯包屑,你可以⽤ props 或者 context 的⽅式进⾏传递。脚⼿架⾥的。
2.2、需求⽰例
2.2.1、新增页⾯
  脚⼿架默认提供了两种布局模板:基础布局 - BasicLayout以及账户相关布局 - UserLayout,如上⽂提供的两种图⽚。
  如果你的页⾯可以利⽤这两种布局,那么只需要在路由及菜单配置中增加⼀条即可:
1》增加路由配置
// src/common/router.js
'/dashboard/test': {
component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Test')),
},
2》菜单配置
// src/common/menu.js
const menuData = [{
name: 'dashboard',
icon: 'dashboard',  // demo/icon.png or <Icon type="dashboard" />
path: 'dashboard',
children: [{
name: '分析页',
path: 'analysis',
}, {
name: '监控页',
path: 'monitor',
}, {
name: '⼯作台',
path: 'workplace',
}, {
name: '测试页',
path: 'test',
}],
}, {
// 更多配置
}];
加好后,会默认⽣成相关的路由及导航。
2.2.2、新增布局模板
  如果提供的布局不能满⾜你的要求,就需要⾃⼰新建 Layout 模板了。假设有两个新的页⾯需要使⽤新模板,你需要先配置好路由及菜单:
1》路由设置
// src/common/router.js
'/new': {
component: dynamicWrapper(app, ['monitor'], () => import('../layouts/NewLayout')),
},
'/new/page1': {
component: dynamicWrapper(app, ['monitor'], () => import('../routes/New/Page1')),
},
'/new/page2': {
component: dynamicWrapper(app, ['monitor'], () => import('../routes/New/Page2')),
},
2》菜单设置
// src/common/menu.js
const menuData = [{
name: '新布局',
icon: 'table',
path: 'new',
children: [{
name: '页⾯⼀',
path: 'page1',
}, {
name: '页⾯⼆',
path: 'page2',
}],
}, {
/
/ 更多配置
}];
在根路由中增加这组新模板:
// src/router.js
<Router history={history}>
<Switch>
<Route path="/new" render={props => <NewLayout {...props} />} />
<Route path="/user" render={props => <UserLayout {...props} />} />
<Route path="/" render={props => <BasicLayout {...props} />} />
</Switch>
</Router>
然后在你的新模板中,仿照src/layouts/BasicLayout.js或src/layouts/UserLayout.js⽣成路由列表即可。2.3、带参数的路由/菜单
脚⼿架默认⽀持带参数的路由、菜单及⾯包屑配置,直接在路由的 key 以及菜单中的 path 配置即可:1》路由
// src/common/router.js
'/dashboard/:workplace': {
component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Workplace')),
},
'/:list/table-list': {
component: dynamicWrapper(app, ['rule'], () => import('../routes/List/TableList')),
},
2》菜单
// src/common/menu.js
const menuData = [{
name: 'dashboard',
icon: 'dashboard',
path: 'dashboard',
children: [{
name: '分析页',
path: 'analysis',
}, {
name: '监控页',
path: 'monitor',
}, {
name: '⼯作台',
path: ':workplace',
}],
}, {
name: '列表页',
icon: 'table',
path: ':list',
children: [],
}, {
// 更多配置
}];
2.4、嵌套布局
  有时在当前 layout 下还需要嵌套其他布局,例如有⼏个页⾯都需要展⽰同⼀个模块,你可以把这部分提炼出来变成⼀个新的布局,再到该布局下⽣成路由列表。与的区别,只是不需要将它增加到根路由中。具体可以参照src/common/router.js /list/search 相关配置,及相关组件⽂件。
2.5、嵌套路由同级展⽰
  脚⼿架默认使⽤⼯具函数 getRoutes 对 routerData 进⾏处理,然后⽣成路由列表,根据,在每⼀级组件中只会渲染当前 match.path 下最邻近的路由,所以,如果你要实现嵌套路由的同级展⽰(如:将/list/search和/list/search/projects在同⼀个地⽅渲染),就需要⼿动获取该路由的数据并添加在合适的地⽅。
{/* src/layouts/BasicLayout.js 类⽐你的上层 layout 组件 */}
<Content style={{ margin: '24px 24px 0', height: '100%' }}>
<div style={{ minHeight: 'calc(100vh - 260px)' }}>
<Switch>
{/* 默认⽣成的路由列表,不包含 /list/search/projects */}
{
getRoutes(match.path, routerData).map(item => (
<Route
key={item.key}
path={item.path}
component={itemponent}
exact={act}
/>
))
}
{/* 补充 /list/search/projects 的路由 */}
<Route exact path="/list/search/projects" component={routerData['/list/search/projects']ponent} />
<Redirect exact from="/" to="/dashboard/analysis" />
<Route render={NotFound} />
</Switch>
</div>
</Content>
同时在嵌套 layout 的⽂件中去掉这⼀条路由(如果还有下层路由需要 render)。
{/* src/routes/List/List.js 类⽐你的嵌套 layout 组件 */}
<Switch>
{
getRoutes(match.path, routerData).filter(item => item.path !== '/list/search/projects').map(item =>
(
<Route
react router路由传参
key={item.key}
path={item.path}
component={itemponent}
exact={act}
/>
)
)
}
</Switch>
2.6、隐藏菜单
如果需要隐藏某条菜单项,可以在该条数据中增加 hideInMenu 参数。
hideInMenu: true,  // 隐藏该条,或隐藏该组
2.7、隐藏⾯包屑
如需隐藏⾯包屑中的某个层级,可以增加 hideInBreadcrumb 属性。
// src/common/router.js
'/dashboard/analysis': {
component: dynamicWrapper(app, ['chart'], () => import('../routes/Dashboard/Analysis')),
hideInBreadcrumb: true,  // 隐藏该条
},
'/dashboard/monitor': {
component: dynamicWrapper(app, ['monitor'], () => import('../routes/Dashboard/Monitor')),
},
2.8、关于 dynamicWrapper
import dynamic from 'dva/dynamic';
// wrapper of dynamic
const dynamicWrapper = (app, models, component) => dynamic({
app,
// eslint-disable-next-line no-underscore-dangle
models: () => models.filter(m => !app._models.some(({ namespace }) => namespace === m)).map(m => import(`../models/${m}.js`)),
// add routerData prop
component: () => {
const routerData = getRouterData(app);
return component().then((raw) => {
const Component = raw.default || raw;
return props => <Component {...props} routerData={routerData} />;
});
},
});import dynamic from 'dva/dynamic';
// wrapper of dynamic
const dynamicWrapper = (app, models, component) => dynamic({
app,
// eslint-disable-next-line no-underscore-dangle
models: () => models.filter(m => !app._models.some(({ namespace }) => namespace === m)).map(m => import(`../models/${m}.js`)),
// add routerData prop
component: () => {
const routerData = getRouterData(app);
return component().then((raw) => {
const Component = raw.default || raw;
return props => <Component {...props} routerData={routerData} />;
});
},
});
为了代码的简洁性,我们对dva/dynamic进⾏了⼆次封装,需要注意的是这⾥使⽤了的动态import,dva的dynamic⽅法已经帮我们封装好了Promise动态加载的相关事宜,所以我们只需要直接使⽤即可。