实现路由守卫需要考虑到以下的问题:
- 未登录情况下,访问不需要权限校验的合法页面:允许访问
- 登陆情况下,访问登陆页面:禁止访问,跳转至主页
- 登陆情况下,访问除登陆页以外的合法页面:允许访问
- 登陆情况下,访问所有的非法页面:禁止访问,跳转至 404
- 未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页
- 未登录情况下,访问所有的非法页面:禁止访问,跳转至 404
react实现路由拦截的基本思路还是利用Route 的render函数。通过判断拦截条件来实现不同的组件的跳转,从而实现拦截。
首先在跟目录src下创建一个routerMap.js文件,代码如下:
import Index from "./page/index";
import Home from "./page/home";
import ErrorPage from "./page/error";
import Login from "./page/login";
export default [
{ path: "/", name: "App", component: Index, auth: true },
{ path: "/home", name: "Home", component: Home, auth: true },
{ path: "/login", name: "Login", component: Login },
{ path: "/404", name: "404", component: ErrorPage },
];
将 auth
设置为 true
,表示该路由需要权限校验。
然后,定义 Router
组件,在App.js中,该组件是经过高阶组件包装后的结果:
//App.js
import React, { Component } from "react";
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
class App extends Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
render() {
return (
<Router>
<div>
<Switch>
<FrontendAuth routerConfig={routerMap} />
</Switch>
</div>
</Router>
);
}
}
export default App;
所有的路由跳转,都交给 FrontendAuth
高阶组件代理完成。下面是 FrontendAuth
组件的实现:
import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
}
render() {
const { routerConfig, location } = this.props;
const { pathname } = location;
const isLogin = sessionStorage.getItem("username");
console.log(pathname, isLogin);
console.log(location);
// 如果该路由不用进行权限校验,登录状态下登陆页除外
// 因为登陆后,无法跳转到登陆页
// 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
const targetRouterConfig = routerConfig.find(
(item) => item.path === pathname
);
console.log(targetRouterConfig);
if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
const { component } = targetRouterConfig;
return <Route exact path={pathname} component={component} />;
}
if (isLogin) {
// 如果是登陆状态,想要跳转到登陆,重定向到主页
if (pathname === "/login") {
return <Redirect to="/" />;
} else {
// 如果路由合法,就跳转到相应的路由
if (targetRouterConfig) {
return (
<Route path={pathname} component={targetRouterConfig.component} />
);
} else {
// 如果路由不合法,重定向到 404 页面
return <Redirect to="/404" />;
}
}
} else {
// 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
if (targetRouterConfig && targetRouterConfig.auth) {
return <Redirect to="/login" />;
} else {
// 非登陆状态下,路由不合法时,重定向至 404
return <Redirect to="/404" />;
}
}
}
}
export default FrontendAuth;
创建登录的界面src/page/login.js,代码如下:
import React, { Component } from "react";
import { Redirect } from "react-router-dom"; //重定向使用
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
rediectToReferrer: false, // 是否重定向之前的界面
};
this.handleChange = this.handleChange.bind(this);
this.handleSumit = this.handleSumit.bind(this);
}
// 处理用户名、密码的变化
handleChange(e) {
if (e.target.name === "username") {
this.setState({
username: e.target.value,
});
} else if (e.target.name === "password") {
this.setState({
password: e.target.value,
});
}
}
// 提交登录表单
async handleSumit(e) {
e.preventDefault();
const username = this.state.username;
const password = this.state.password;
if (username.length === 0 || password.length === 0) {
alert("用户名或密码不能为空!");
return;
}
// 保存信息到sessionStorage
sessionStorage.setItem("username", username);
// 登录成功后,设置redirectToReferrer为true;
this.setState({
rediectToReferrer: true,
});
let RedirectUrl = this.props.location.state
? this.props.location.state.from.pathname
: "/";
// 登陆成功之后的跳转
this.props.history.push(RedirectUrl);
}
render() {
return (
<form className="login" onSubmit={this.handleSumit}>
<div>
<label htmlFor="">
用户名:
<input
type="text"
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
</label>
</div>
<div>
<label htmlFor="">
密码:
<input
type="password"
name="password"
value={this.state.password}
onChange={this.handleChange}
/>
</label>
</div>
<input type="submit" value="登录" />
</form>
);
}
}
export default Login;
页面上的路由跳转,都由 FrontendAuth
高阶组件代理了,在 Switch
组件内部,不再是 Route
组件,而只有一个 FrontendAuth
组件。FrontendAuth
组件接收一个名为 config
的 Props
,这是一份路由表。同时,由于 FrontendAuth
组件放在了 Switch
组件内部,React Router 还自动为 FrontendAuth
注入了 location
属性,当地址栏的路由发生变化时,就会触发 location
属性对象上的 pathname
属性发生变化,从而触发 FrontendAuth
的更新(调用 render
函数)。FrontendAuth
的 render
函数中,根据 pathname
查找到路由表中的相关配置,如果该配置中指定了无需校验,就直接返回相应的 Route
组件。
如果查找到的配置需要进行校验,再根据是否登陆进行处理,具体可以查看代码中的注释。