比如以后要改下路径,除了config.js中需要改一次,代码全篇幅都需要改一次,好累;name该怎么办呢?以下我们就来处理一下路由的优化!
优化思路:
最好能在一个地方去维护这些路径地址,也就是config.js中,name我们就需要把config中的menuGlobal存储一份到全局配置的store中,这样将来项目其他地方有用到路径的,均可以从tore中取出来使用即可。
那这里我们就考虑,存在store中的这路由数据,应该是什么结构呢,没错,我们使用immutable数据的Map形式,易操作,简单直观(之前我们在menuGlobal总预留的id和pid这里就要用到了)。具体如下:
1 修改utils/config.js如下:
- import {OrderedSet,Map,fromJS} from 'immutable'
- const menuGlobal=[
- {
- id:'login',
- pid:'0',
- name:'登录',
- icon:'user',
- path: '/login',
- models: () => [import('../models/login')], //models可多个
- component: () => import('../routes/login'),
- },
- {
- id:'home',
- pid:'0',
- name:'首页',
- icon:'user',
- path: '/',
- models: () => [import('../models/home')], //models可多个
- component: () => import('../routes/home'),
- },
- {
- id:'aaa',
- pid:'0',
- name:'aaa页',
- icon:'user',
- path: '/aaa',
- models: () => [import('../models/aaa')], //models可多个
- component: () => import('../routes/AAA'),
- },
- {
- id:'bbb',
- pid:'0',
- name:'bbb页',
- icon:'user',
- path: '/aaa/bbb',
- models: () => [import('../models/bbb')], //models可多个
- component: () => import('../routes/BBB'),
- },
- {
- id:'ccc',
- pid:'0',
- name:'ccc页',
- icon:'user',
- path: '/ccc',
- models: () => [import('../models/ccc')], //models可多个
- component: () => import('../routes/CCC'),
- },
- ];
- /**
- * 封装路由数据,利用id和pid的关联性处理
- */
- const menuMap = (() => {
- let byId = Map();
- let byPid = Map();
- menuGlobal.map(item => {
- byId = byId.set(item.id, fromJS(item));
- byPid = byPid.update(item.pid, obj => obj ? obj.add(item.id) : OrderedSet([item.id]))
- });
- return Map({
- byId,
- byPid
- });
- })();
- export default {
- menuGlobal,
- menuMap
- }
2 修改models/app.js如下:
- import {Map, fromJS} from 'immutable';
- import {routerRedux} from 'dva/router';
- import {config} from '../utils';
- const {menuMap} = config;
- const initState = Map({
- i18n: 'zh_CN',
- token:null,
- locationPathname:null,
- menu:menuMap
- })
- export default {
- namespace: 'app',
- state:initState,
- subscriptions: {
- setup({ dispatch, history }) {
- },
- setupHistory ({ dispatch, history }) {
- history.listen((location) => {
- dispatch({
- type: 'updateLocation',
- payload: {
- locationPathname: location.pathname
- },
- });
- dispatch({
- type: 'updateToken',
- payload: {
- token: window.sessionStorage.getItem('token')
- },
- })
- })
- },
- },
- effects: {
- * changeLang ({
- payload: {value},
- }, { put }) {
- yield put({ type: 'updateLang', payload: {value}});
- },
- * updateLocation ({
- payload
- }, {put, select}) {
- yield put({type: 'updateStore', payload});
- },
- * updateToken ({
- payload
- }, {put, select}) {
- yield put({type: 'updateStore', payload});
- },
- * loginOk ({
- payload
- }, {put, select}) {
- window.sessionStorage.setItem('token',payload.token);
- yield put(routerRedux.push({
- pathname: '/'
- }));
- },
- * logout ({
- payload
- }, {put, select}) {
- window.sessionStorage.removeItem('token');
- window.location.href='/login';
- },
- },
- reducers: {
- updateLang (state,{payload:{value}}) {
- return state.set('i18n',value);
- },
- updateStore (state, { payload }) {
- return payload?state.mergeDeep(fromJS(payload)):initState
- },
- },
- };
以上修改做了两件事,1封装menuMap数据结构,2存储在app的model中menu对象,效果如下:
接下来,我们就可以在任何地方获取并使用它了
(1)在组件中使用,需要connect数据 menu:app.get('menu'),以routes/home/index.js为例,修改如下:
- import React, {Component} from 'react';
- import {connect} from 'dva'
- import {Link} from 'dva/router'
- import {injectIntl} from 'react-intl'
- import {Row, Col, Form, Button} from 'antd'
- import classnames from 'classnames';
- import styles from './index.less';
- class Home extends Component{
- render(){
- const {menu} = this.props;
- const aaaUrl=menu.getIn(['byId','aaa','path']);
- return(
- <Row>
- <Col className={classnames(styles.home)}>
- 欢迎您,来到首页
- </Col>
- <Col>
- <Link to={aaaUrl}><Button>去AAA页面</Button></Link>
- </Col>
- </Row>
- )
- }
- }
- export default connect(({
- app
- })=>({
- menu:app.get('menu')
- }))(injectIntl(Form.create()(Home)))
(2)在models中使用,需要select数据 yield select(_=>_.app.getIn(['menu','byId','home','path'])), 以models/app.js为例,修改loginOk如下:
- import {Map, fromJS} from 'immutable';
- import {routerRedux} from 'dva/router';
- import {config} from '../utils';
- const {menuMap} = config;
- const initState = Map({
- i18n: 'zh_CN',
- token:null,
- locationPathname:null,
- menu:menuMap
- })
- export default {
- namespace: 'app',
- state:initState,
- subscriptions: {
- setup({ dispatch, history }) {
- },
- setupHistory ({ dispatch, history }) {
- history.listen((location) => {
- dispatch({
- type: 'updateLocation',
- payload: {
- locationPathname: location.pathname
- },
- });
- dispatch({
- type: 'updateToken',
- payload: {
- token: window.sessionStorage.getItem('token')
- },
- })
- })
- },
- },
- effects: {
- * changeLang ({
- payload: {value},
- }, { put }) {
- yield put({ type: 'updateLang', payload: {value}});
- },
- * updateLocation ({
- payload
- }, {put, select}) {
- yield put({type: 'updateStore', payload});
- },
- * updateToken ({
- payload
- }, {put, select}) {
- yield put({type: 'updateStore', payload});
- },
- * loginOk ({
- payload
- }, {put, select}) {
- const homeUrl = yield select(_=>_.app.getIn(['menu','byId','home','path']))
- window.sessionStorage.setItem('token',payload.token);
- yield put(routerRedux.push({
- pathname: homeUrl
- }));
- },
- * logout ({
- payload
- }, {put, select}) {
- window.sessionStorage.removeItem('token');
- window.location.href='/login';
- },
- },
- reducers: {
- updateLang (state,{payload:{value}}) {
- return state.set('i18n',value);
- },
- updateStore (state, { payload }) {
- return payload?state.mergeDeep(fromJS(payload)):initState
- },
- },
- };
下面给大家推荐一款路由正则匹配的工具:path-to-regexp,用来更简易的操作路径匹配问题,有兴趣的同学可以自行学习。
在首页增加一个路由,用作编辑和新增页面,修改utils/config.js如下:
- import {OrderedSet,Map,fromJS} from 'immutable'
- const menuGlobal=[
- {
- id:'login',
- pid:'0',
- name:'登录',
- icon:'user',
- path: '/login',
- models: () => [import('../models/login')], //models可多个
- component: () => import('../routes/login'),
- },
- {
- id:'home',
- pid:'0',
- name:'首页',
- icon:'user',
- path: '/',
- models: () => [import('../models/home')], //models可多个
- component: () => import('../routes/home'),
- },
- {
- id:'home-edit',
- pid:'home',
- name:'首页-编辑和新增',
- icon:'user',
- path: '/edit/:id?',
- models: () => [import('../models/home')], //models可多个
- component: () => import('../routes/home/edit'),
- },
- {
- id:'aaa',
- pid:'0',
- name:'aaa页',
- icon:'user',
- path: '/aaa',
- models: () => [import('../models/aaa')], //models可多个
- component: () => import('../routes/AAA'),
- },
- {
- id:'bbb',
- pid:'0',
- name:'bbb页',
- icon:'user',
- path: '/aaa/bbb',
- models: () => [import('../models/bbb')], //models可多个
- component: () => import('../routes/BBB'),
- },
- {
- id:'ccc',
- pid:'0',
- name:'ccc页',
- icon:'user',
- path: '/ccc',
- models: () => [import('../models/ccc')], //models可多个
- component: () => import('../routes/CCC'),
- },
- ];
- /**
- * 封装路由数据,利用id和pid的关联性处理
- */
- const menuMap = (() => {
- let byId = Map();
- let byPid = Map();
- menuGlobal.map(item => {
- byId = byId.set(item.id, fromJS(item));
- byPid = byPid.update(item.pid, obj => obj ? obj.add(item.id) : OrderedSet([item.id]))
- });
- return Map({
- byId,
- byPid
- });
- })();
- export default {
- menuGlobal,
- menuMap
- }
对应增加组件routes/home/edit.js代码如下:
- import React, {Component} from 'react';
- import {connect} from 'dva'
- import {Link} from 'dva/router'
- import {injectIntl} from 'react-intl'
- import {Row, Col, Form, Button} from 'antd'
- import classnames from 'classnames';
- import styles from './index.less';
- class Home extends Component{
- goBack=()=>{
- const {dispatch} = this.props;
- dispatch({
- type:'app/goBack'
- })
- }
- render(){
- const {menu,match} = this.props;
- const id=match.params.id;
- return(
- <Row>
- <Col className={classnames(styles.home)}>
- 欢迎您,来到home<span style={{fontSize:'24px'}}>{id?`编辑${id}`:`新增`}</span>页面
- </Col>
- <Col>
- <Button onClick={this.goBack}>返回</Button>
- </Col>
- </Row>
- )
- }
- }
- export default connect(({
- app
- })=>({
- menu:app.get('menu')
- }))(injectIntl(Form.create()(Home)))
这里用到了返回上一页,只需要在models/app.js中增加effects,用于返回上一页
- * goBack ({
- payload
- }, {put, select}) {
- yield put(routerRedux.goBack());
- },
修改routes/home/index.js,灵活使用pathToRegexp.compile(homeEditUrl)({id:1}) 如下:
- import React, {Component} from 'react';
- import {connect} from 'dva'
- import {Link} from 'dva/router'
- import {injectIntl} from 'react-intl'
- import {Row, Col, Form, Button} from 'antd'
- import classnames from 'classnames';
- import pathToRegexp from 'path-to-regexp'
- import styles from './index.less';
- class Home extends Component{
- render(){
- const {menu} = this.props;
- const aaaUrl=menu.getIn(['byId','aaa','path']);
- const homeEditUrl=menu.getIn(['byId','home-edit','path']);
- return(
- <Row>
- <Col className={classnames(styles.home)}>
- 欢迎您,来到首页
- </Col>
- <Col>
- <Link to={aaaUrl}><Button>去AAA页面</Button></Link>
- <Link to={pathToRegexp.compile(homeEditUrl)()}><Button>新增</Button></Link>
- <Link to={pathToRegexp.compile(homeEditUrl)({id:1})}><Button>编辑(id=1)</Button></Link>
- <Link to={pathToRegexp.compile(homeEditUrl)({id:2})}><Button>编辑(id=2)</Button></Link>
- <Link to={pathToRegexp.compile(homeEditUrl)({id:123})}><Button>编辑(id=123)</Button></Link>
- </Col>
- </Row>
- )
- }
- }
- export default connect(({
- app
- })=>({
- menu:app.get('menu')
- }))(injectIntl(Form.create()(Home)))
然后修改models/home.js,灵活使用pathToRegexp(homeUrl).exec(pathname) 如下:
- import pathToRegexp from 'path-to-regexp'
- export default {
- namespace: 'home',
- state: {
- name:'这是home的model'
- },
- subscriptions: {
- setup({ dispatch, history }) {
- return history.listen(({ pathname, query }) => {
- dispatch({type: 'dataInit', payload: {pathname}});
- });
- },
- },
- effects: {
- * dataInit({payload: {pathname}}, {put,call,select}){
- const homeUrl = yield select(_=>_.app.getIn(['menu','byId','home','path']));
- const homeEditUrl = yield select(_=>_.app.getIn(['menu','byId','home-edit','path']));
- if(pathToRegexp(homeUrl).exec(pathname)){
- console.log('home页面执行')
- }else if(pathToRegexp(homeEditUrl.substring(0,homeEditUrl.lastIndexOf('?')-4)).exec(pathname)){
- console.log('home-新增页面执行')
- }else if(pathToRegexp(homeEditUrl.substring(0,homeEditUrl.lastIndexOf('?'))).exec(pathname)){
- console.log('home-编辑页面执行')
- }
- },
- },
- reducers: {
- },
- };
好了,我们的预期是:
(1)访问https://localhost:9999/,控制台输出“home页面执行”
(2)访问https://localhost:9999/edit,控制台输出“home-新增页面执行”
(3)访问https://localhost:9999/edit/1,控制台输出“home-编辑页面执行”
下面看看效果:
哦了,就是这个样子!
再给大家介绍一个常用的工具react-helmet,什么东西呢,直观翻译‘头盔’,用在react组件中,就是用来处理页面的head部分构成的,有兴趣的同学自行学习。安装:cnpm i react-helmet --save
先来看看现在的页面title:
增加/layout/layout.js文件,代码:
- import {connect} from 'dva';
- import React from 'react';
- import pathToRegexp from 'path-to-regexp'
- import Helmet from 'react-helmet';
- const Layout=({ children,dispatch,menu,locationPathname })=>{
- const menuList=menu.getIn(['byId']).toList();
- let menuName='';
- menuList.map(item=>{
- if(pathToRegexp(item.get('path')).exec(locationPathname)){
- menuName = item.get('name');
- }
- });
- return (
- <React.Fragment>
- <Helmet>
- <title>
- {menuName}
- </title>
- </Helmet>
- {children}
- </React.Fragment>
- );
- }
- export default connect(({
- app
- })=>({
- menu:app.get('menu'),
- locationPathname:app.get('locationPathname'),
- }))(Layout)
修改/layout/auth.js 如下:
- import {connect} from 'dva';
- import React from 'react';
- import Layout from './layout';
- const Auth=({ children,dispatch,token,locationPathname })=>{
- if(!token&&locationPathname!='/login'){
- dispatch({
- type:'app/logout'
- })
- }else if(token&&locationPathname=='/login'){
- dispatch({
- type:'app/loginOk',
- payload:{
- token:token
- }
- })
- }
- return (
- <Layout>
- {children}
- </Layout>
- );
- }
- export default connect(({
- app
- })=>({
- token:app.get('token'),
- locationPathname:app.get('locationPathname'),
- }))(Auth)
OK,我们再来看页面title:
此时,title显示的已经是menuGlobal中配置的name值了。