react路由集中管理及鉴权 react-router-config

react路由集中管理及鉴权 react-router-config

 

git地址:https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config
react-router-config主要用来帮助我们进行集中式路由的配置,在不使用react-router-config之前,我们的路由使用react-router-dom库来进行配置,类似如下代码:

import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import Backend from './pages/Backend';
import Admin from './pages/Admin';

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/login" component={Login}/>
        <Route path="/backend" component={Backend}/>
        <Route path="/admin" component={Admin}/>
        <Route path="/" component={Home}/>
      </Switch>
    </Router>
  );
}

export default App;

react-router-config可以使得路由配置更加简便

routes.ts路由配置

import { RouteConfig } from 'react-router-config';
import Home from '../components/Home';
import Inquiry from '../components/Inquiry';
import Offer from '../components/Offer';

const routes: RouteConfig = [
    {
        path: '/',
        component: Home,
        routes: [
            {
                path: '/inquiry',
                component: Inquiry
            },
            {
                path: '/offer',
                component: Offer
            }
        ]
    }
]

export default routes

index.js入口文件

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
import './index.css';
import { renderRoutes } from 'react-router-config';
import { HashRouter } from 'react-router-dom';
import routes from './config/routes';
import stores from './stores/index';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
 <HashRouter>
     <Provider {...stores}>
        {renderRoutes(routes)}
     </Provider>
 </HashRouter>,
 document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Home组件:

import React from 'react';
import { renderRoutes } from 'react-router-config';

const Home = (props: any) => { 
     const route = props.route;

     return <div>
         <div>this is Home page.</div>
         {route && renderRoutes(route.routes)}
     </div>
}

export default Home;

Inquiry组件:

import React from 'react';

const Inquiry = () => {
    return <div>Inquiry</div>
}

export default Inquiry;

Offer组件

import React from 'react';

const Offer = () => {
    return <div>Offer</div>
}

export default Offer;

下面代码实现了react-router-config的功能

1.实现集中式路由配置

router.js

import React, { Suspense, lazy } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import { matchPath, Router } from "react-router"

// component: React.lazy(() => import("pages/table-poc"))
// component: lazy(() => import("@/pages/user/All"))
// component: require('../a/b/index.js').default,

//const Game = React.lazy(() => new Promise( (resolve) => {
//    setTimeout( () => resolve(import('./Game')) , 1000)
//}))
// 使用React自带的 Suspense,lazy实现懒加载
const routes = [
    { path: "/", exact: true, render: () => <Redirect to={"/login/password"} /> },
    { path: "/login", exact: true, render: () => <Redirect to={"/login/password"} /> },
    { path: "/principal", exact: true, render: () => <Redirect to={"/principal/teacher"} /> },
    { path: "/teacher", exact: true, render: () => <Redirect to={"/teacher/textbook"} /> },
    {
        path: '/login/:type',
        name: 'Login',
        component: lazy(() => import("pages/login/login.jsx")),
        meta: { title: '登录' }
    },
    {
        path: '/password',
        name: 'Login',
        component: lazy(() => import("pages/login/login.jsx")),
        meta: { title: '修改密码' }
    },

    {
        path: '/teacher',
        name: 'Teacher',
        component: lazy(() => import('pages/layout/layout.jsx')),
        meta: { title: '教师端' },
        routes: [
            {
                path: '/teacher/textbook',
                name: 'TeacherTextbook',
                component: lazy(() => import('pages/teacher/textbook/textbook.jsx')),
                meta: { title: '选课' },
                routes: [
                    {
                        path: '/teacher/textbook/index',
                        name: 'TeacherTextbookIndex',
                        component: lazy(() => import('pages/test.jsx')),
                        meta: { title: '测试啊' },
                    }
                ]
            },
            {
                path: '/teacher/in-class/:id/:name',
                name: 'TeacherInclass',
                component: lazy(() => import('pages/teacher/in-class/in-class.jsx')),
                meta: { title: '上课' }
            }
        ]
    }
]

// 实现react-router-config里的renderRoutes方法
function renderRoutes (routes, extraProps = {}, switchProps = {}) {
    return routes ? (
        <Suspense fallback={<div>页面加载中...</div>}>
            <Switch {...switchProps}>
                {routes.map((route, i) => (
                    <Route
                        key={route.key || i}
                        path={route.path}
                        exact={route.exact}
                        strict={route.strict}
                        render={props =>
                            route.render ? (
                                route.render({ ...props, ...extraProps, route: route })
                            ) : (
                                <route.component {...props} {...extraProps} route={route} />
                            )
                        }
                    />
                ))}
            </Switch>
        </Suspense>
    ) : null;
}

// 实现react-router-config里的matchRoutes方法
function matchRoutes (routes, pathname, /*not public API*/ branch = []) {
    routes.some(route => {
        const match = route.path
            ? matchPath(pathname, route)
            : branch.length
                ? branch[branch.length - 1].match // use parent match
                : Router.computeRootMatch(pathname); // use default "root" match
        
                if (match) {
            branch.push({ route, match });

            if (route.routes) {
                matchRoutes(route.routes, pathname, branch);
            }
        }
        return match;
    });
    return branch;
}

export { routes, renderRoutes, matchRoutes }

App.js

import './App.scss';
import { BrowserRouter } from 'react-router-dom'
import { routes, renderRoutes } from './router'

function App () {
  return (
    <BrowserRouter>
      {/* 这个方法,每次有子路由时都需要使用,会传当前路由的子路由,可以说是按需加载,
       实时编译的,而不是一次性吧所有路由都渲染出来 */}
      {renderRoutes(routes)}
    </BrowserRouter>
  )
}

export default App

其他页面调用时:{renderRoutes(this.props.route.routes)}

2.实现面包屑
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { renderRoutes, matchRoutes, routes } from 'router'

export default class Layout extends Component {
    render () {
        const branch = matchRoutes(routes, this.props.location.pathname)
        return (
            <div>
                <header>
                    {/* 面包屑 */}
                    {
                        branch.map((item, i) => {
                            return item.match.url === pathname ?
                                <span key={i}>{item.route.meta.title}</span> :
                                <Link to={item.match.path} key={i} className='back'>{item.route.meta.title}</Link>
                        })
                    }
                </header>
                {/* 路由 */}
                {renderRoutes(nowRoutes)}
            </div>
        )
    }
}

3.路由鉴权
以下是个人重写,添加了多重权限验证以及多重路由匹配(即添加渲染非<Switch>包裹的<Route>)。

export interface IRouteConfig extends RouteConfig {
  auth?: number;
  routes?: IRouteConfig[];
  multipleRoutes?: IRouteConfig[];
}

/**
 * 将路由配置渲染成节点
 * @param routes switch路由列表
 * @param authed 当前账号权限
 * @param multipleRoutes 非switch路由列表,将会在Switch节点前渲染Route
 * @param extraProps 添加额外的Route props
 * @param switchProps Switch props
 */
function renderRoutes(
  routes: IRouteConfig[] | undefined,
  authed: number,
  multipleRoutes?: IRouteConfig[],
  extraProps?: any,
  switchProps?: SwitchProps
) {
  const isMobile = checkMobile();
  let list = [];
  const mapFunc = (R: IRouteConfig[]) =>
    R.map((route, i) => (
      <Route
        key={route.key || i}
        path={route.path}
        exact={route.exact}
        strict={route.strict}
        render={props => {
          // 将authed赋值到route,试子组件可以通过route.authed获取当前用户权限
          if (authed !== undefined) route.authed = authed;
          // 不存在authed或者authed大于当前路由权限,即可渲染组件,否则跳转登录界面
          if (!route.auth || route.auth <= authed) {
            return route.render
              ? route.render({ ...props, ...extraProps, route: route })
              : route.component && (
                  <route.component {...props} {...extraProps} route={route} />
                );
          } else {
            message.warn("请先登录!");
            return (
              <Redirect to={route.auth <= 1 "/user/loginRegister/login"} />
            );
          }
        }}
      />
    ));
  if (routes) {
    list.push(
      <Switch {...switchProps} key="biubiubiu~~">
        {mapFunc(routes)}
      </Switch>
    );
    // 将非Switch包裹的Route挂载到Switch节点之前
    multipleRoutes && list.unshift(...mapFunc(multipleRoutes));
    // 返回一个数组,[<Route/>,...,<Route/>,<Switch>...</Switch>](实际元素并非如此结构,此处仅为方便说明写的伪代码),React会将数组渲染成节点
    return list;
  }
}

修改后的routes,当匹配到"/user/all/article/(id值)"的路径,页面会同时渲染Article以及All两个组件,未登录则渲染Article和Login组件,mutipleRoutes主要是为了能通过多重匹配路由模拟VUE的keep-alive效果。代码如下👇:

export const mobileRouterList: IRouteConfig[] = [
  {
    path: "/",
    exact: true,
    render: () => <Redirect to="/user/all" />
  },
  {
    path: "/user",
    component: MobileMain,
    multipleRoutes: [
      { path: "/user/:page/article/:id", component: Article }
    ],
    routes: [
      {
        path: "/user/all",
        component: lazy(() => import("@/pages/user/All"))
      },
      {
        path: "/user/me",
        auth: 1, // 用户权限必须 >=1 才可以访问
        component: lazy(() => import("@/pages/user/Me"))
      },
      {
        path: "/admin/controlPanel",
        auth: 2, // 用户权限必须 >=2(管理员) 才可以访问
        component: lazy(() => import("@/pages/admin/ControlPanel"))
      }
    ]
  }
]

修改后的rednerRoutes使用,后面参数选填:

// 顶层使用路由渲染,手动添加第二参数
renderRoutes(routes,user.authed)
// 子层组件路由渲染
renderRoutes(props.route.routes,<props.route.authed>,<props.route.multipleRoutes>,...)

以下是自己封装的路由鉴权(判断是否登录以及页面的访问权限)

import React, { Suspense, lazy } from 'react'
import { Redirect } from 'react-router-dom'
import { Route, Switch } from 'react-router'
// exact属性为true时路径中的hash值必须和path完全一致才渲染对应的组件,如果为false则'/'也可以匹配'/login';
// (如果strict属性为false,则末尾是否包含反斜杠结尾不影响匹配结果)

// strict属性主要就是匹配反斜杠,规定是否匹配末尾包含反斜杠的路径,如果strict为true,则如果path中不包含反斜杠结尾,
// 则他也不能匹配包含反斜杠结尾的路径,这个需要和exact结合使用

const permissions = 'user'//登录接口获取的当前用户的角色
const requiresAuth = true //是否已经登录

//所有的路由
export const routes = [
  {
    path: '/',
    exact: true,
    requiresAuth: false,
    permissions: ['user', 'admin'],
    render: () => <Redirect to='/login' />
  },
  {
    path: '/login',
    name: 'Login',
    exact: true,
    strict: true,
    requiresAuth: false,//是否需要登录
    permissions: ['user', 'admin'], // 当前登录权限必须 user或admin 才可以访问
    component: lazy(() => import('../pages/login.jsx')),//路由懒加载
    meta: { title: '登录呢', icon: 'login' }
  },
  {
    path: '/JsPlan1',
    name: 'JsPlan1',
    exact: true,
    requiresAuth: true,
    permissions: ['user', 'admin'],
    component: lazy(() => import('../pages/js/plan1.jsx')),
    meta: { title: 'js的plan1', icon: 'user' }
  },
  {
    path: '/JsPlan2',
    name: 'JsPlan2',
    exact: true,
    requiresAuth: true,
    permissions: ['admin'],
    component: lazy(() => import('../pages/js/plan2.jsx')),
    meta: { title: 'js的plan2' }
  },
  {
    path: '/TsPlan1',
    name: 'TsPlan1',
    exact: true,
    requiresAuth: true,
    permissions: ['admin'],
    component: lazy(() => import('../pages/ts/plan1.tsx')),
    meta: { title: 'ts的plan1' }
  },
  {
    path: '/TsPlan2',
    name: 'TsPlan2',
    exact: true,
    requiresAuth: true,
    permissions: ['user', 'admin'],
    component: lazy(() => import('../pages/ts/plan2.tsx')),
    meta: { title: 'ts的plan2' }
  },
]

export const renderRoutes = (routes, extraProps = {}, switchProps = {}) => {
  return routes ? (
    <Suspense fallback={<div>页面加载中...</div>}>
      <Switch {...switchProps}>
        {routes.map((item, i) => (
          <Route
            key={item.name || i}
            path={item.path}
            exact={item.exact}
            strict={item.strict}
            render={props => routeRender(item, props, extraProps)}
          />
        ))}
      </Switch>
    </Suspense>
  ) : null
}

const routeRender = (route, props, extraProps) => {
  // 登录判断(需要登录 && 未登录-->跳登录页面,,,,,,,不需要登录 || 已经登录-->正常跳转)
  const login = route.requiresAuth && !requiresAuth //跳登录
  // 该角色是否有权限访问该页面(当前角色是否在 路由要求角色数组中)
  const auth = route.permissions.includes(permissions) //有权限
  console.log(222, '是需要跳转登录页面' + login, '是否有权限' + auth, props)
  // 判断渲染route
  if (login) {
    return <Redirect to={{ path: '/login', message: '请登录后再操作!' }} />
  } else {
    if (auth) {
      return route.render ? (
        route.render({ ...props, ...extraProps, route: route })
      ) : (
        <route.component {...props} {...extraProps} route={route} />
      )
    } else {
      alert('您暂无权限')
      return <Redirect to={{ path: '/login', message: '请登录后再操作!' }} />
    }
  }
}
posted @ 2022-05-19 00:24  小羊不挑食  阅读(660)  评论(0编辑  收藏  举报