上篇文章中,介绍了利用高阶组件,在 React Router 4 中实现了路由守卫。这样的路由守卫,是在前端路由层面上的守卫,属于页面校验机制的第一层:用户能否访问到相应的页面。
事实上,在页面校验时,还有一层需要进行处理:访问接口时的身份认证。某些时候,当用户登陆了系统,可以进入到某个受保护的页面,但在请求接口时,用户的身份令牌(比如 Token)失效了,这种情况下就不能让用户继续留在受保护的页面,而应该重定向至登录页,通过登陆获取新的有效的身份令牌。
一般来说,用户在访问每个接口时,都需要进行一次身份校验,如果用户身份认证失败(HTTP 状态码返回 401),就需要重定向至登录页。但在每次接口调用后,都进行一次判断的话非常麻烦,难以维护。如果我们使用的网络请求的库能够配置响应拦截器就好了,这样就可以对响应进行统一的拦截处理,只需要一份代码就可以处理身份校验。
Axios 中就有这样的功能:axios.interceptors.response
响应拦截器(对应的有 axios.interceptors.request
请求拦截器)。
下面是 Axios 的响应拦截器配合 React Router 4 的一个例子:
import axios from 'axios';
import { message } from 'antd'
import { ERROR } from '../config/error.config';
import createHistory from 'history/createHashHistory';
// 响应拦截
axios.interceptors.response.use((response) => {
return response
}, (err) => {
if(err.response.status === '401'){
message.error(ERROR.LOGIN_EXPIRESSED);
localStorage.removeItem('__config_center_token');
localStorage.removeItem('__config_center_niceName');
localStorage.removeItem('__config_center_imageUrl');
const history = createHistory();
setTimeout(() => {
history.push('/login')
},1500)
}
return Promise.reject(err)
})
当身份认证失败时,首先进行全局的跳转提示,然后移除 LocalStorage
中保存的原始数据,再通过 createHashHistory
的实例(我在项目中使用的是 HashHistory
,如果使用的是 BrowserHistory
的话,需要引入 creatBrowserHistory
)进行路由的跳转。
在使用 React Router 4 时,只有在 Route
组件内部才能通过 Props
获取到 history
实例,而在非 Route
组件内部想再使用 history
的功能的话,只有通过 createHashHistory
再创建一个了。
一点优化建议
上面的请求中,每个请求都会被拦截,如果一次只进行一个请求还好说,但如果同时请求多个接口,假如此时身份验证失败了,就会进行多次的弹窗提醒以及路由跳转,这个就很不友好,因此,对于一次性发出多个请求的情况,如果这些请求都失败了,最好只弹窗提醒一次。我的解决方案是加一个哨兵变量:
import { message } from 'antd'
import { ERROR } from '../config/error.config';
import createHistory from 'history/createHashHistory';
let cancelFlag:boolean = false;
// 响应拦截
axios.interceptors.response.use((response) => {
return response
}, (err) => {
if(err.response.status === '401'){
if(cancelFlag) return Promise.reject(err);
cancelFlag = true;
message.error(ERROR.LOGIN_EXPIRESSED);
localStorage.removeItem('__config_center_token');
localStorage.removeItem('__config_center_niceName');
localStorage.removeItem('__config_center_imageUrl');
const history = createHistory();
setTimeout(() => {
history.push('/login')
setTimeout(() => {
cancelFlag = false;
},1000)
},1500)
}
return Promise.reject(err)
})
以上,就避免了多次弹窗提醒和路由跳转的情况。
完。
网友评论