路由守卫
vue-router
中有beforeEnter
这样的路由守卫,
可以获得从哪个路由过来的(from)
,要到哪个路由去(to)
,
是放行还是拦截(next)
,而react-router-dom
中却没有。
老版本还有个onEnter
可以用用,现在什么都没了。
因为作者说了,为了灵活性,不加入路由守卫方面的api,
也就是说,你要根据自己的需求写路由守卫。
需求
先看下我们的路由守卫都需要干些什么,
首先,我们需要知道对哪些路由进行拦截,
其次,我们也要知道从哪个路由来,到哪个路由去。
举个例子,
我想看一篇博客,于是先来到主页路由/boke?bokeId=2333
,由于未登录,
路由被拦截,要跳转到/login
路由去。当用户在这里登录后,
为了增加用户体验,
需要自动回到原来的路由/boke?bokeId=2333
去。不然用户登录后却找不到
原来想看的博客了,那就尴尬了。
知识预备
要实现上述需求,就需要准备些知识,下面一一梳理。
Navigate跳转(重定向)
react-router-dom@6
中,是使用Navigate
组件进行重定向的,
而且跳转时,还可以通过state
携带数据给目标路由组件。
比如
import { Navigate } from 'react-router-dom';
<Navigate to='/login' replace={true}
state={{from: this.state.from}} />
注意,这里携带数据只能用state
变量,不能用其他的,
不然在目标路由组件中获取不到。下面会讲为什么。
useLocation获取跳转携带的数据
import { useLocation } from 'react-router-dom';
const Login = () => {
const local = useLocation();
console.log('local: ', local)
}
打印一下通过useLocation()
得到的值可以看到:
local
是个对象,有hash
key
pathname
search
state
这五个属性,
其中,state
就是跳转传递过来的数据。这也就解释了,跳转的时候,
为啥一定要用state
作为参数,而不能用其他的。
class组件及react18的生命周期
为啥要了解这个呢?
因为我们要拦截路由,在页面渲染前进行判断操作,
而旧的生命周期componentWillMount
将要被废弃,我们需要用新的生命周期
来处理。而这些生命周期需要在class
组件中使用。
首先来看一张新的生命周期图:
在新的生命周期里,在render
之前,有三个生命周期会执行,它们分别是:
constructor
static getDerivedStateFromProps()
shouldComponentUpdate()
在第一个里面,我们一般只定义初始状态,不做其他的操作,所以不选;
第三个,官方明确指出,不要用它来阻止页面渲染,不然可能会出现
意料之外的bug。
所以我们只能选第二个。
static getDerivedStateFromProps()
该生命周期在render
之前执行,有两个
参数,分别是props
state
,最终会return
一个对象,可以是null
,
如果不是null
,就表示状态更新了,返回的就是最新状态。
我们刚好需要更新两个状态,一个是我们是否需要往登录页跳转,
另一个是我们需要通过状态获取到跳转之前的路由。
高阶组件
高阶组件是一个函数,接收一个组件作为参数,返回的是一个新的组件。
代码实现
我把路由相关内容拆分成了3个文件,以便看起来清爽一点
ele.js
用来存放路由路径,
lazySuspense.jsx
用来实现路由守卫和懒加载,
index.js
就是用来返回最终路由组件
先来看路由守卫:
import React, { Suspense } from 'react';
import { Navigate } from 'react-router-dom';
import { profile } from '@depjs/storage';
class RouteGuard extends React.Component {
constructor(props) {
super(props);
this.state = {
isToLoginPage: false, // 判断是否要跳转到登录页,false为不跳转
from: '' // 从哪个路由跳转到登录页
};
}
// 在页面渲染前进行拦截、判断
static getDerivedStateFromProps(props, state) {
const l = window.location;
const href = l.pathname + l.search;
// console.log('href: ', href);
const _state = {
isToLoginPage: false,
from: ''
}
// 当前路由不是登录页的路由,并且未登录,
// 就修改状态,跳转到登录页为true,然后给from赋值
if (!href.includes('/login') && !(profile.userName && profile.token)) {
_state.isToLoginPage = true;
_state.from = href;
}
return _state; // 返回最新状态
}
render() {
const { Com } = this.props;
// 跳转时,使用state把from路由数据传递给登录页
// 这里有个判断,如果要跳转到登录页,就用Navigate去跳转
// 如果不需要跳转,就渲染对应的路由组件
const ToCom = this.state.isToLoginPage
? <Navigate to='/login' replace={true} state={{from: this.state.from}} />
: <Suspense fallback={<div>loading...</div>}>
<Com />
</Suspense>
return (
ToCom
)
}
}
// 高阶组件,用于传递数据,生成新组件
const lazySuspense = (Com) => {
return <RouteGuard Com={Com} />
}
export default lazySuspense;
再来看ele.js
import { lazy } from 'react';
// 引入路由守卫
import lazySuspense from './lazySuspense';
import Main from '@pages/main02';
import Page404 from '@components/404';
const Login02 = lazy(() => import('@pages/login02'));
const Calendar = lazy(() => import('@pages/calendar'));
const Todos = lazy(() => import('@pages/calendar/todos'));
const CustomizeFestival = lazy(() => import('@pages/calendar/customizeFestival'));
const ele = [
{
path: '/',
element: lazySuspense(Main)
},
{
path: '/login',
element: lazySuspense(Login02)
},
{
path: '/calendar',
element: lazySuspense(Calendar)
},
{
path: '/todos',
element: lazySuspense(Todos)
},
{
path: '/customizeFestival',
element: lazySuspense(CustomizeFestival)
},
{
path: '*',
element: <Page404 /> // 注意,404页面是不需要路由守卫的!
}
]
export default ele;
再来看最终得到的路由组件index.js
import { useRoutes } from 'react-router-dom';
import ele from './ele';
const App = () => {
const routes = useRoutes(ele); // 生成路由组件
return routes;
}
export default App
最后来看看登录页Login02.jsx
的处理
// 其他引入省略...
import { useLocation } from 'react-router-dom';
const Login02 = () => {
const local = useLocation();
console.log('local: ', local)
}
项目地址
还有不清楚的地方,可以直接去看源码:
https://gitee.com/guozia007/gogo/blob/master/client/src/router/index.js