0
点赞
收藏
分享

微信扫一扫

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

灵魂跑者 03-31 20:30 阅读 2

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

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

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

image-20240327093023168

然后梳理一下流程:

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

  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中麻烦多了

实现步骤

  1. 在 components 目录中,创建 AuthRoute/index.jsx 文件
  2. 登录时,直接渲染相应页面组件
  3. 未登录时,重定向到登录页面
  4. 将需要鉴权的页面路由配置,替换为 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;
举报

相关推荐

0 条评论