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,这样不易报错),编写如下代码:
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中也是这么用的
二、页面登录时执行异步方法
先上代码
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.Itemname="mobile"rules={[{required: true,message: "请输入手机号!",},{pattern: /^1[3-9]\d{9}$/,message: "手机号码格式不对",},]}><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Itemname="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回调方法
const onFinish = async (values) => {await dispatch(fetchToken(values))navigate('/')message.success('登录成功')};
首先,values就是用户填写的登录信息{mobile: ‘xxxxxx’, code: ‘246810’}
然后,执行redux中的异步方法,获取token,并存储token
最后,路由跳转,并通知用户登录成功
三、请求拦截器中注入token
向后端发送请求,为了保证数据安全,一般都需要携带token,vue写了很多相关的博客了,原理一样,为保证内容的连贯性,我放上相关的代码
在封装的axios函数中,编写以下代码
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的方法,代码如下:
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
代码
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
代码
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中编写路由守卫相关的代码就行了,我的一般写法如下:
// 路由守卫
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;