一、初始路由设计
token在初始界面时一般涉及两个组件:Layout和Login
import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
const router=createBrowserRouter([
{
path:"/",
element: <Layout />
},
{
path: "/login",
element: <Login />
},
])
export default router
Login作为登录表单页负责获取token对其进行管理存储,后跳转到其他子组件出口的面板页Layout
二、封装token的存取删除的方法
对于token的各类操作复用性较强,可以直接封装成utils函数
//封装token相关的方法 存 取 删
//如果有单独的常量文件,可以此条语句写入并导入当前文件
const TOKENKEY = 'token_key'
function setToken(token) {
localStorage.setItem(TOKENKEY, token)
}
function getToken() {
return localStorage.getItem(TOKENKEY)
}
function removeToken() {
return localStorage.removeItem(TOKENKEY)
}
export { setToken, getToken, removeToken }
三、使用Redux管理token
token作为一个用户的标识数据,需要在很多个模块中实现状态共享
1.redux中编写获取token的异步获取和同步修改action
//src/store/modules/user.js
//用户相关的管理
import { createSlice } from "@reduxjs/toolkit";
import { request } from "@/utils";
const userStore=createSlice({
name:'user',
//初始状态
initialState:{
token: ''
},
//同步修改方法
reducers:{
setToken(state,action){
state.token=action.payload
}
}
})
//解构出actionCreater
const {setToken} =userStore.actions
//获取reducer函数
const userReducer=userStore.reducer
//异步方法 完成登录获取token
const fetchLogin=(loginForm)=>{
return async (dispatch)=>{
//1.发送异步请求
const res=await request.post('/authorizations',loginForm)
//2.提交同步方法
dispatch(setToken( res.data.token))
}
}
export { setToken, fetchLogin }
export default userReducer
2.Login组件负责提交action并把表单数据传递过来
//src/pages/Login/index.js
//点击登录回调,values自动获取表单数据
const onFinish = async (values) => {
//key为设置的name值
//触发异步action fetchLogin
//保证token获取到之后才进行跳转工作
await dispatch(fetchLogin(values))
//跳转首页
navigate('/')
//提示
message.success('登录成功')
}
四、token持久化
问题:
redux是基于浏览器内存的存储方式,刷新浏览器时状态会恢复为初始值,token会丢失
解决方案:
1.获取token时:同时存入redux和本地localStorage
//src/store/modules/user.js
import { setToken as _setToken,getToken } from "@/utils";
//同步修改方法
reducers:{
setToken(state,action){
state.token=action.payload
//存进本地
_setToken(action.payload)
}
}
2.初始化token时:在redux中先查看本地是否有token值,如果有则用本地值,没有就置为空字符串
//src/store/modules/user.js
import { setToken as _setToken,getToken } from "@/utils";
initialState:{
//先从本地取,如果本地有,就用本地的
token: getToken() ||''
},
五、在axios中注入token
1.在请求拦截器中配置token请求头
// 添加请求拦截器
// 在请求发送之前 做拦截 插入一些自定义的配置 [参数的处理]
request.interceptors.request.use((config) => {
// 操作这个config 注入token数据
// 1. 获取到token
// 2. 按照后端的格式要求做token拼接
const token = getToken()
if (token) {
//根据自身情况是否拼接字符串
config.headers.Authorization = `Bearer ${token}`
}
return config
}, (error) => {
return Promise.reject(error)
})
2.在响应拦截器中处理token失效
// 添加响应拦截器
// 在响应返回到客户端之前 做拦截 重点处理返回的数据
request.interceptors.response.use((response) => {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data
}, (error) => {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
// 监控401 token失效
console.dir(error)
if (error.response.status === 401) {
removeToken()
router.navigate('/login')
//无法跳转登录页,会因为报错卡在这里,此时强制刷新即可
window.location.reload()
}
return Promise.reject(error)
})
注意:navigate和useNavigate的区分
import router from "@/router"
router.navigate('/login')
//写在函数外
import { useNavigate } from 'react-router-dom'
//写在函数中
const navigate = useNavigate()
navigate('/')
两种都可以进行页面导航,他们的区别是什么呢?
1.
useNavigate
- 用法:
useNavigate
是一个 React Hook,用于在函数组件中进行导航。- 特点:
- 只能在函数组件中使用。
- 直接返回一个
navigate
函数,用于实现导航。2.
router.navigate
- 用法:
router.navigate
通常是在 React Router 的更高层次的 API(如在类组件中或路由配置中)中使用的。- 特点:
- 可能需要对
router
对象有更多的上下文理解。- 一般用于类组件或在特定的上下文中。
笼统地说,就是useNavigate只能用于函数组件中,而像request.js这样的工具函数用navigate
六、使用token做路由权限控制
有些路由页面内的内容信息比较敏感,如果用户没有经过登录获取到有效的token,是没有权限跳转的,根据token的有无控制当前路由是否可以跳转就是路由的权限控制
1.封装高阶组件
// 封装高阶组件
// 核心逻辑: 有token 正常跳转 无token 去登录
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
export function AuthRoute ({ children }) {
const token = getToken()
if (token) {
//接收到组件正常渲染
return <>{children}</>
} else {
//重定向 to跳转路径 replace替换当前为根路径
return <Navigate to={'/login'} replace />
}
}
2.修改路由
import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { AuthRoute } from "@/components/AuthRoute";
import { createBrowserRouter } from "react-router-dom";
const router=createBrowserRouter([
{
path:"/",
element: <AuthRoute ><Layout /></AuthRoute>
},
{
path: "/login",
element: <Login />
},
])
export default router