react项目开发-路由优化(前三篇续)

目前的项目架构,大家都能看到,有路由跳转的地方都是写死的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)在组件中使用,需要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值了。


猜你喜欢

转载自blog.csdn.net/xw505501936/article/details/80654769
今日推荐