目前的项目架构,大家都能看到,有路由跳转的地方都是写死的url,如<Link to={'/aaa'}>,push({pathname:'/login'}),这种方式呢,没有问题,但是这样写死不太好,不便于后期维护,
比如以后要改下路径,除了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.png
接下来,我们就可以在任何地方获取并使用它了
(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-编辑页面执行”

下面看看效果:
2.gif

哦了,就是这个样子!

再给大家介绍一个常用的工具react-helmet,什么东西呢,直观翻译‘头盔’,用在react组件中,就是用来处理页面的head部分构成的,有兴趣的同学自行学习。安装:cnpm i react-helmet --save
先来看看现在的页面title:
3.png
增加/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:
4.png
5.png
此时,title显示的已经是menuGlobal中配置的name值了。

标签: none

添加新评论

选择表情