react+redux完成登录页面及token的存取和路由守卫
关于登录页面,我在写vue项目的时候,写了很多篇博客来记录。原因是登录确实比较复杂,涉及前后端联调、全局数据管理、浏览器本地存储等多个环节的技术。框架换成react后,逻辑是一样的,但是技术栈及语法却完全不一样了,有必要记录一下整个过程。
首先看看登录界面,简单的两个输入框,和一个登录按钮,用的antDesign的UI框架
然后梳理一下流程:
- 首先在项目中基于redux设置一个全局数据,也就是token,token是后端返回的带有用户信息的一串字符,不再赘述token的作用,反正就是和用户相关的所有页面都需要用到它
- redux中编写保存token的同步方法,以及获取token的异步方法,因为token是通过接口向后端请求的,所以要使用异步方法
- 页面提交登录数据,点击提交时发送异步请求,获取token
- token持久化存储
一、全局数据管理
首先要安装react-redux
store中新建user.jsx(我用的vite创建项目,最好将js文件都改成jsx,这样不易报错),编写如下代码:
javascript
import { createSlice } from "@reduxjs/toolkit";
import http from '../../utils/http'
const userStore = createSlice({
name: "user",
initialState: {
token: localStorage.getItem('token_key') || "",
},
reducers: {
setToken(state, action) {
state.token = action.payload;
localStorage.setItem('token_key', action.payload)
},
},
});
const { setToken } = userStore.actions;
const userReducer = userStore.reducer;
// 异步方法,登录获取token
const fetchToken = (loginForm) => {
return async (dispatch) => {
const res = await http.post('/authorizations', loginForm)
// const res = await http.post('/user/login', loginForm)
// 提交同步action进行token保存
dispatch(setToken(res.data.token))
}
}
export { setToken, fetchToken };
export default userReducer;
同步方法写在reducer中,即setToken方法,用于将全局token修改为获取到的token,在获取token后,同时在localstorage中设置token_key
异步方法就是fetchToken,实际上就是搜集用户填写的登录信息,并基于此信息向后端发起异步请求,然后调用dispatch提交同步action执行setToken函数
有个需要主意的,token的初始化localStorage.getItem('token_key') || "",
,其实在vue中也是这么用的
二、页面登录时执行异步方法
先上代码
javascript
import "./index.scss";
import { Card, Form, Input, Button, message } from "antd";
import logo from "../../assets/logo.png";
import { useState } from "react";
import { fetchToken } from "../../store/modules/user";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const onFinish = async (values) => {
console.log(values);
await dispatch(fetchToken(values))
navigate('/')
message.success('登录成功')
};
return (
<div className="login">
<Card className="login-container">
<img className="login-logo" src={logo} alt="" />
{/* 登录表单 */}
<Form validateTrigger={["onBlur"]} onFinish={onFinish}>
<Form.Item
name="mobile"
rules={[
{
required: true,
message: "请输入手机号!",
},
{
pattern: /^1[3-9]\d{9}$/,
message: "手机号码格式不对",
},
]}
>
<Input size="large" placeholder="请输入手机号" />
</Form.Item>
<Form.Item
name="code"
rules={[
{
required: true,
message: "请输入验证码!",
},
]}
>
<Input size="large" placeholder="请输入验证码" maxLength={6} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" size="large" block>
登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
);
};
export default Login;
核心就是上面的onfinish回调方法
javascript
const onFinish = async (values) => {
await dispatch(fetchToken(values))
navigate('/')
message.success('登录成功')
};
首先,values就是用户填写的登录信息{mobile: 'xxxxxx', code: '246810'}
然后,执行redux中的异步方法,获取token,并存储token
最后,路由跳转,并通知用户登录成功
三、请求拦截器中注入token
向后端发送请求,为了保证数据安全,一般都需要携带token,vue写了很多相关的博客了,原理一样,为保证内容的连贯性,我放上相关的代码
在封装的axios函数中,编写以下代码
javascript
import axios from "axios";
import { getToken } from "./token";
const http = axios.create({
baseURL: "http://geek.itheima.net/v1_0",
timeout: 5000,
});
// axios请求拦截器
http.interceptors.request.use(
(config) => {
const token = getToken()
if (token) {
config.headers.Authorization = "Bearer " + token;
}
return config;
},
(e) => Promise.reject(e)
);
// axios响应式拦截器
http.interceptors.response.use(
(res) => res.data,
(e) => {
console.log(e);
return Promise.reject(e);
}
);
export default http;
其中,getToken是封装的从localstorage中取token的方法,代码如下:
javascript
const TOKENKEY = "token_key";
function setToken(token) {
return localStorage.setItem(TOKENKEY, token);
}
function getToken() {
return localStorage.getItem(TOKENKEY);
}
function clearToken() {
return localStorage.removeItem(TOKENKEY);
}
export { setToken, getToken, clearToken };
四、路由鉴权/路由守卫
在vue中,叫路由守卫,写在router/index.js中,只有用户登录成功后,也就是说有了token后,才能访问其他页面,vue中的路由守卫我也写了很多相关的博客了,可以参考,react中的做法要比vue中麻烦多了
实现步骤
- 在 components 目录中,创建
AuthRoute/index.jsx
文件 - 登录时,直接渲染相应页面组件
- 未登录时,重定向到登录页面
- 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染
components/AuthRoute/index.jsx
代码
javascript
import { getToken } from '../../utils/token'
import { Navigate } from 'react-router-dom'
const AuthRoute = ({ children }) => {
const isToken = getToken()
if (isToken) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
export default AuthRoute
然后修改路由
src/router/index.jsx
代码
javascript
import { createBrowserRouter } from 'react-router-dom'
import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'
const router = createBrowserRouter([
{
path: '/',
element: <AuthRoute><Layout /></AuthRoute>,
},
{
path: '/login',
element: <Login />,
},
])
export default router
而vue中,只需要是router/index.js中编写路由守卫相关的代码就行了,我的一般写法如下:
javascript
// 路由守卫
import jwt_decode from "jwt-decode";
router.beforeEach((to, from, next) => {
const isLogin = localStorage.user ? true : false;
if (isLogin) {
const user = JSON.parse(localStorage.user)
const decode = jwt_decode(user.userInfo.token);
const date = parseInt(new Date().getTime() / 1000);
if (date - decode.iat > decode.exp - decode.iat) {
localStorage.removeItem("user");
next("/login");
}
}
if (to.path == "/login") {
next();
} else {
isLogin ? next() : next("/login");
}
});
export default router;