react+redux完成登录页面及token的存取和登录保持

react+redux完成登录页面及token的存取和路由守卫

关于登录页面,我在写vue项目的时候,写了很多篇博客来记录。原因是登录确实比较复杂,涉及前后端联调、全局数据管理、浏览器本地存储等多个环节的技术。框架换成react后,逻辑是一样的,但是技术栈及语法却完全不一样了,有必要记录一下整个过程。

首先看看登录界面,简单的两个输入框,和一个登录按钮,用的antDesign的UI框架

然后梳理一下流程:

  1. 首先在项目中基于redux设置一个全局数据,也就是token,token是后端返回的带有用户信息的一串字符,不再赘述token的作用,反正就是和用户相关的所有页面都需要用到它
  2. redux中编写保存token的同步方法,以及获取token的异步方法,因为token是通过接口向后端请求的,所以要使用异步方法
  3. 页面提交登录数据,点击提交时发送异步请求,获取token
  4. 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中麻烦多了

实现步骤

  1. 在 components 目录中,创建 AuthRoute/index.jsx 文件
  2. 登录时,直接渲染相应页面组件
  3. 未登录时,重定向到登录页面
  4. 将需要鉴权的页面路由配置,替换为 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;
相关推荐
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60617 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi10 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马12 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒12 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端