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;