比如以后要改下路径,除了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值了。