1. 因為 react-router4 沒有在提供 onEnter 這樣的全局跳轉鈎子,所以要通過 高階組件 去處理 來實現一個 路由守衛
2. 按需加載這里我同樣使用 高階組件 來實現
3. 登錄成功時 要先獲取用戶菜單保存到redux中,在登錄狀態下,刷新頁面需要重新獲取菜單,並保存到redux中,方便在路由守衛中直接從redux中拿到菜單數據來填充進主體頁面路由文件中去。
ps: 只在這里記錄一下嘗試的核心代碼部分,實際效果可在 github 上克隆代碼后 運行項目查看
一、路由守衛
守衛 routerComponent.js相關代碼

1 import React, { Component } from 'react'; 2 3 import { Route, Redirect } from 'react-router-dom'; 4 5 import { renderRoutes } from 'react-router-config'; 6 7 import {asyncComponent as async} from '@/utils/asyncComponent.js'; 8 9 import store from '@/store/index'; 10 11 import Main from '@/router/main'; 12 13 14 class RouterAuth extends Component { 15 constructor(props) { 16 super(props); 17 18 this.state = { 19 20 }; 21 } 22 23 //根據菜單生成路由文件 24 handleRouters(menu){ 25 let childRouter = []; 26 menu.forEach((item) => { 27 if(!!item.childs){ 28 childRouter = [...childRouter, ...this.handleRouters(item.childs)]; 29 }else{ 30 let component = item.component; 31 let path = item.path; 32 //根據es6module語法,由於import是靜態執行,所以不能使用表達式和變量, 33 //解決方法 es6模板字符串 import(`./path/${myFile}.jsx`)。 34 // 注意: 35 // ${myFile}變量前邊一定要寫一個帶"/"的字符串。 36 // ".jsx" 不能寫在變量里,要寫在字符串里。 37 //目前只能一個頁面對應一個js,如何按模塊對應js? 38 item.component = async(()=>import(/* webpackChunkName: "[request]" */ `@/${component}.jsx`)); 39 return childRouter.push(item) 40 } 41 }) 42 return childRouter 43 } 44 45 render() { 46 let { location, config } = this.props; 47 let { pathname } = location; 48 console.log(location, config, pathname) 49 50 let token = localStorage.getItem('token'); 51 52 let targetRouterConfig = config.find((item) => item.path === pathname); 53 54 55 //如果是登錄狀態 56 if(!!token){ 57 //如果進入登錄頁面,則直接重定向至首頁 58 if(pathname === '/login' || pathname === '/'){ 59 return <Redirect to='/home' /> 60 }else{ 61 //如果路由存在 62 if(targetRouterConfig){ 63 //如果是需要登錄的或者是404頁面則直接進入 64 if(targetRouterConfig.auth || pathname === '/404'){ 65 let { component } = targetRouterConfig; 66 return <Route exact path={pathname} component={component} /> 67 }else{//否則重定向到首頁 68 return <Redirect to='/home' /> 69 } 70 }else{ 71 //判斷沒有設置權限菜單,則根據菜單設置上 72 if(Main[0].routes.length == 0){ 73 let menu = store.getState().user.menu; 74 let menus = this.handleRouters(menu); 75 Main[0].routes = menus; 76 } 77 //如果菜單中包含當前路由,則進入 78 let menuConfig = Main[0].routes.filter((item) => { 79 return item.path === pathname 80 }); 81 if(menuConfig.length != 0){ 82 return renderRoutes(Main); 83 }else{//不包含則進入404 84 return <Redirect to='/404' /> 85 } 86 87 } 88 } 89 }else{ //非登錄狀態 90 //如果路由存在 91 if(targetRouterConfig){ 92 //如果需要登錄,則跳轉到登錄頁 93 if(targetRouterConfig.auth){ 94 return <Redirect to='/login' /> 95 }else{//不需要登錄,則正常進入 96 let { component } = targetRouterConfig; 97 return <Route exact path={pathname} component={component} /> 98 } 99 }else{ 100 //路由不存在,直接進入登錄頁 101 return <Redirect to='/login' /> 102 } 103 } 104 } 105 } 106 107 export default RouterAuth;
路由 index.js文件代碼

1 import React from 'react'; 2 3 import { HashRouter, Switch, Route, Redirect } from "react-router-dom"; 4 5 import RouterAuth from '@/utils/routerComponent'; 6 7 /* 8 HashRouter 9 1.用這個了就代表路徑加上/#/ 10 2.換成BrowserRouter了;路徑就不需要加/#/ 11 3.用HashRouter了就要把path的路徑名字帶上,如果首次加載默認的話要這樣寫: <Route exact path="/" component={App}/> 12 */ 13 14 import Login from './login'; 15 16 import Main from './main'; 17 18 import NotFound from './notFound'; 19 20 const routes = [ 21 ...Login, 22 ...NotFound 23 ]; 24 25 const BasicRoute = () => ( 26 <HashRouter> 27 <Switch> 28 <RouterAuth config={routes}></RouterAuth> 29 </Switch> 30 </HashRouter> 31 ); 32 33 export default BasicRoute;
二、按需加載
按需加載 asyncComponent.js 代碼

1 import React from 'react' 2 3 export const asyncComponent = loadComponent => ( 4 class AsyncComponent extends React.Component { 5 constructor(props) { 6 super(props); 7 this.state = { 8 Component: null 9 } 10 } 11 12 UNSAFE_componentWillMount() { 13 if (this.hasLoadedComponent()) { 14 return 15 } 16 17 loadComponent() 18 .then(module => module.default) 19 .then((Component) => { 20 this.setState({Component}) 21 }) 22 .catch((err) => { 23 console.error(`Cannot load component in <AsyncComponent />`); 24 throw err 25 }) 26 } 27 28 hasLoadedComponent() { 29 return this.state.Component !== null 30 } 31 32 render() { 33 const {Component} = this.state; 34 return (Component) ? <Component {...this.props} /> : null 35 } 36 } 37 )
三、獲取菜單相關
登錄成功時代碼

1 //登錄 2 onFinish = (values) => { 3 api.login(values).then((result) => { 4 //登錄成功后需要先獲取下菜單,然后在跳轉至主頁 5 //獲取個人信息與系統菜單 6 common.getMenu().then((res) => { 7 if(res.success){ 8 store.dispatch({ 9 type: 'USER_MENU', 10 data: res.data.menu 11 }) 12 localStorage.setItem('token', result.token); 13 this.props.history.push('/home'); 14 } 15 }) 16 }) 17 }
刷新頁面時代碼

1 //如果是登錄狀態,則刷新頁面時需先獲取到菜單后才能掛載路由頁面 2 let token = localStorage.getItem('token'); 3 if(!!token){ 4 //獲取個人信息與系統菜單 5 api.getMenu().then((res) => { 6 if(res.success){ 7 store.dispatch({ 8 type: 'USER_MENU', 9 data: res.data.menu 10 }) 11 12 ReactDOM.render( 13 <Router />, 14 document.getElementById('root') 15 ); 16 } 17 }) 18 }else{ 19 ReactDOM.render( 20 <Router />, 21 document.getElementById('root') 22 ); 23 }