尝试 React16、React-router4 实现根据动态菜单生成按需加载的路由
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 }