vite+react+ts如何集成redux状态管理工具,实现持久化缓存

1.安装插件

这里的redux-persist--进行数据的持久化缓存,确保页面刷新数据不会丢失

yarn add react-redux@^9.2.0 redux-persist@^6.0.0 @reduxjs/toolkit@^2.5.1

2.创建仓库文件夹

在项目的src文件夹下创建名为store的文件夹,里面的具体文件如下

features文件夹对应文件如下

features文件夹中存放对应的需要进行状态管理的数据

couterSlice.ts

这里是一个简单的计数器的加减,赋值,清零的一个测试仓库

createSlice中包含的字段

initialState存储的字段

reducers对应useDispatch的分发行为

name仓库的唯一标识

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "../store";

// 为 slice state 定义一个类型
interface CounterState {
  value: number;
}

// 使用该类型定义初始 state
const initialState: CounterState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  // `createSlice` 将从 `initialState` 参数推断 state 类型
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // 使用 PayloadAction 类型声明 `action.payload` 的内容,用于登录,注册等传入具体的参数
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
    //清零
    emptyCount: (state, action: PayloadAction<number>) => {
      state.value = action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount, emptyCount } =
  counterSlice.actions;
// 选择器等其他代码可以使用导入的 `RootState` 类型
export const selectCount = (state: RootState) => state.counter.value;

export default counterSlice.reducer;
user.ts

存储用户信息以及模拟登录token的reducer

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "../store";
import {
  getToken,
  setToken,
  getLocal,
  setLocal,
  removeToken,
  removeLocal,
} from "@/utils/token";
import { Local } from "@/enums/Local";

interface UserState {
  token: string | undefined | null;
  username: string | undefined | null;
}

const initialState: UserState = {
  token: getToken() || undefined,
  username: getLocal(Local.USER_INFO)?.username || undefined,
};

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setInfo: (state, action: PayloadAction<UserState>) => {
      state.token = action.payload.token;
      state.username = action.payload.username;

      setToken(action.payload.token);
      setLocal(Local.USER_INFO, { username: action.payload.username });
    },
    remove: (state) => {
      state.token = undefined;
      state.username = undefined;
      removeToken();
      removeLocal(Local.USER_INFO);
    },
  },
});

export const { setInfo, remove } = userSlice.actions;
// 选择器等其他代码可以使用导入的 `RootState` 类型
export const selectUser = (state: RootState) => state.user;

export default userSlice.reducer;

user.ts文件夹下使用了一些方法,补充如下

utils文件夹下的token.ts文件 以及enums文件夹下的Loacl文件如下
import { Local } from "@/enums/Local";
/**
 * @Description: 生成一个随机token
 * @Date: 2024-11-20 17:14:19
 */
export function generateToken(length: number) {
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let token = "";
  for (let i = 0; i < length; i++) {
    const randomIndex = Math.floor(Math.random() * characters.length);
    token += characters[randomIndex];
  }
  return token;
}

/**
 * @Description: 将token存储到localStorage中
 * @Date: 2024-11-20 17:15:12
 */
export function setToken(token: string) {
  const expireTime = new Date().getTime() + 24 * 60 * 60 * 1000; // 设置当前时间加上24小时为过期时间
  const data = {
    token: token,
    expire: expireTime,
  };
  localStorage.setItem("token", JSON.stringify(data));
}

/**
 * @Description: 从localStorage中获取token
 * @Date: 2024-11-20 17:15:42
 */
export function getToken() {
  const dataString = localStorage.getItem("token");
  if (dataString) {
    const data = JSON.parse(dataString);
    const currentTime = new Date().getTime();

    if (currentTime > data.expire) {
      // 如果过期,则删除token
      removeToken();
      return null; // token已过期,返回null
    }

    return data.token; // 返回有效的token
  }
  return null; // 如果没有token,返回null
}

/**
 * @Description: 将localStorage中的token删除
 * @Date: 2024-11-20 17:16:02
 */
export function removeToken() {
  localStorage.removeItem("token"); // 删除token
}

/**
 * @Description: 设置本地存储
 * @Date: 2024-12-05 14:16:45
 */
export function setLocal(key: Local, value: any) {
  localStorage.setItem(key, JSON.stringify(value));
}

/**
 * @Description: 获取本地存储
 * @Date: 2024-12-05 14:19:02
 */
export function getLocal(key: Local) {
  const dataString = localStorage.getItem(key);
  if (dataString) {
    console.log(JSON.parse(dataString));
    
    return JSON.parse(dataString);
  } else {
    return null;
  }
}

/**
 * @Description: 清除本地存储
 * @Date: 2024-12-05 14:19:48
 */
export function removeLocal(key: Local) {
  localStorage.removeItem(key);
}
export enum Local {
  USER_INFO = "USER_INFO",
  REDIRECT_PATH = "redirectPath",
}

hook.d.ts

定义这个声明文件,允许将它们导入到任何需要使用的 hooks 的组件文件中,并避免潜在的循环导入的依赖问题。

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

store.ts仓库入口文件如下

使用persistConfig进行持久化配置,root为存储到本地的名称,没有特别的要求,whiteList白名单,存放的是需要持久化存储的仓库blackList则相反

import { configureStore } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; // 默认使用 localStorage
import counterReducer from "./features/couterSlice";
import userReducer from "./features/user";
// ...

// 持久化配置
const persistConfig = {
  key: "root", // 存储的键名
  storage, // 存储方式
  whitelist: ["user"], // 需要持久化的reducer
  blacklist: ["counter"], // 不需要持久化的reducer
};

const persistedReducer = persistReducer(persistConfig, userReducer);

const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: persistedReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // 忽略 redux-persist 的持久化 action
        ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"],
      },
    }),
});

const persistor = persistStore(store);

// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>;
// 推断出类型: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;

export { store, persistor };

在入口文件main.tsx引入store.ts并配置

采取provider上下文的方式实现 数据的传输 使用PersistGate组件对白名单中的reducer进行持久化存储

import { createRoot } from "react-dom/client";
import "./index.css";
import { HashRouter } from "react-router-dom";
import App from "./App.tsx";
import { store, persistor } from "./store/store.ts";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";

createRoot(document.getElementById("root")!).render(
  <HashRouter>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </HashRouter>
);

3.使用仓库

通过useSelector从redux的state中读取对应的值

useSelector((state: any) => state?.'仓库名称'.value)

useDispatch ,用于在组件中分发操作(action),从而更新 Redux store 中的数据。

从对应的reducer中引入对应的分发操作,因为reduxjs/toolkit的特性,再进行传参赋值的时候,不必要再像老版本一样{...旧值,新值}

传参赋值参考emptyCount这一函数等

使用conuterSlice仓库
import { useSelector, useDispatch } from "react-redux";
import {
  decrement,
  increment,
  incrementByAmount,
  emptyCount,
} from "@/store/features/couterSlice";
import { setInfo, remove } from "@/store/features/user";
import { Button } from "antd";

function DefaultPage() {
  const count = useSelector((state: any) => state?.counter.value);
  const userInfo = useSelector((state: any) => state?.user);
  const dispatch = useDispatch();

  return (
    <>
      <div>
        <div>
          <Button
            className="mr-4"
            onClick={() => dispatch(emptyCount(0))}
            type="primary"
          >
            清零
          </Button>
          <Button type="primary" onClick={() => dispatch(increment())}>
            increment增加
          </Button>
          <div className="mt-2 mb-2">{count}</div>
          <Button
            type="primary"
            className="mr-4"
            onClick={() => dispatch(decrement())}
          >
            Decrement减少
          </Button>
          <Button
            type="primary"
            onClick={() => dispatch(incrementByAmount(count > 0 ? count : 1))}
          >
            增加指定值
          </Button>
         
        </div>
      </div>
    </>
  );
}

export default DefaultPage;
补充:结合user.ts这一reducer实现鉴权登录。

这里需要封装一个鉴权组件判断是否登录,给需要鉴权登录的组件嵌套上即可

import React from "react";
import { Local } from "@/enums/Local";
import { setLocal } from "@/utils/token";
import { Navigate, useLocation, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux"; // 假设你使用 Redux

// 这是一个路由守卫组件,用于检查用户是否登录
const RequireAuth = ({ children }: { children: React.ReactNode }) => {

  const location = useLocation();
  const userInfo = useSelector((state: any) => state?.user);
  // 如果用户未登录,则保存当前路径并重定向到登录页面
  if (!userInfo?.token) {
    setLocal(Local.REDIRECT_PATH, location.pathname);
    return <Navigate to="/login" />;
  }

  return children;
};

export default RequireAuth;
登录页面

这里在用户名密码都正确后 采用useDispatch调用reducer中的serInfo方法 将用户名和token存储到storage 和 仓库中去,登录成功后重定向到第一次访问的默认页

import { Button, Checkbox, Form, Input, message } from "antd";
import { Local } from "@/enums/Local";
import { generateToken, getLocal, removeLocal } from "@/utils/token";
import { setInfo } from "@/store/features/user";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import type { FormProps } from "antd";
import "./index.less";

function Login() {
  const [messageApi, contextHolder] = message.useMessage();
  const myCleft = "c_left";
  const myCright = "c_right";
    
  const userList = [
  {
    userName: "admin",
    password: "123456",
  },
  {
    userName: "student",
    password: "123456",
  },
];


  type FieldType = {
    username?: string;
    password?: string;
  };

  const dispatch = useDispatch();
  const navigate = useNavigate();

  const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
    console.log("Success:", values);
    checkInfo(values);
  };

  const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
    errorInfo
  ) => {
    console.log("Failed:", errorInfo);
  };

  //判断用户名密码是否正确
  const checkInfo = (value: FieldType) => {
    //用户名
    const userIndex = userList.findIndex(
      (item) => item.userName == value.username
    );
    if (userIndex == -1) {
      messageApi.open({
        type: "error",
        content: "用户名错误",
        duration: 1,
      });
      return;
    } else {
      const passwordIndex = userList.findIndex(
        (item) => item.password == value.password
      );
      if (passwordIndex == -1) {
        messageApi.open({
          type: "error",
          content: "密码错误",
          duration: 1,
        });
      } else {
        messageApi
          .open({
            type: "loading",
            content: "登录中",
            duration: 1.5,
          })
          .then(() => {
            messageApi
              .open({
                type: "success",
                content: "登录成功",
                duration: 1,
              })
              .then(() => {
                handleLogin(value);
              });
          });
      }
    }
  };

  //登录
  const handleLogin = (value: FieldType) => {
    const token = generateToken(16);
    dispatch(setInfo({ token, username: value.username }));
    // 登录成功后
    const redirectPath = getLocal(Local.REDIRECT_PATH) || "/";
    removeLocal(Local.REDIRECT_PATH); // 清除路径
    navigate(redirectPath, { replace: true }); // 重定向
  };

  return (
    <>
      {contextHolder}
      <div className="w-full h-full " style={{ background: "lightgray" }}>
        <div className="w-full h-full flex justify-center items-center">
          <div className="l_container">
            <div className="c_left">
              <div className={`${myCleft}-title`}>欢迎您的到来!</div>
              <div className={`${myCleft}-title`}>WELCOME !</div>
              <div className={`${myCleft}-desc`}>
                请在右侧输入账号密码,登录您的账号
              </div>
            </div>
            <div className="c_right">
              <div className={`${myCright}-title`}>请输入信息!</div>
              <div className={`${myCright}-form`}>
                <Form
                  name="basic"
                  labelCol={{ span: 8 }}
                  wrapperCol={{ span: 16 }}
                  style={{ maxWidth: 600 }}
                  initialValues={{ remember: true }}
                  onFinish={onFinish}
                  onFinishFailed={onFinishFailed}
                  autoComplete="off"
                >
                  <Form.Item<FieldType>
                    label="用户名:"
                    name="username"
                    rules={[
                      {
                        required: true,
                        message: "请输入用户名称!",
                      },
                    ]}
                  >
                    <Input />
                  </Form.Item>

                  <Form.Item<FieldType>
                    label="密码:"
                    name="password"
                    rules={[
                      {
                        required: true,
                        message: "请输入密码!",
                      },
                    ]}
                  >
                    <Input.Password />
                  </Form.Item>

                  <Form.Item label={null}>
                    <Button
                      className="sub_btn"
                      type="primary"
                      size="large"
                      htmlType="submit"
                    >
                      登录
                    </Button>
                  </Form.Item>
                </Form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

export default Login;

参考git地址如下:

myReactRouterOnlyRead: 仅供参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型function calling:让AI函数调用更智能、更高效

大模型function calling&#xff1a;让AI函数调用更智能、更高效 随着大语言模型&#xff08;LLM&#xff09;的快速发展&#xff0c;其在实际应用中的能力越来越受到关注。Function Calling 是一种新兴的技术&#xff0c;允许大模型与外部工具或API进行交互&#xff0c;从而扩…

图像分类项目1:基于卷积神经网络的动物图像分类

一、选题背景及动机 在现代社会中&#xff0c;图像分类是计算机视觉领域的一个重要任务。动物图像分类具有广泛的应用&#xff0c;例如生态学研究、动物保护、农业监测等。通过对动物图像进行自动分类&#xff0c;可以帮助人们更好地了解动物种类、数量和分布情况&#xff0c;…

在 Ansys Maxwell 中分析磁场

在 Ansys Maxwell 中分析磁场 分析磁场的能力对于理解电磁系统至关重要。Ansys Maxwell 为工程师提供了强大的工具&#xff0c;帮助他们探索磁场数据并从中提取有价值的见解。在本指南中&#xff0c;我将深入研究 Ansys Maxwell 中的几种基本技术和方法&#xff0c;以有效地分…

[Lc滑动窗口_1] 长度最小的数组 | 无重复字符的最长子串 | 最大连续1的个数 III | 将 x 减到 0 的最小操作数

目录 1. 长度最小的字数组 题解 代码 ⭕2.无重复字符的最长子串 题解 代码 3.最大连续1的个数 III 题解 代码 4.将 x 减到 0 的最小操作数 题解 代码 1. 长度最小的字数组 题目链接&#xff1a;209.长度最小的字数组 题目分析: 给定一个含有 n 个 正整数 的数组…

数据库Redis数据库

目录 一、数据库类型 1、关系型数据库 2、非关系型数据库 3、关系型非关系型区别 二、Redis数据库 1、什么是Redis 3、Redis特点 4、Redis为什么读写快 5、部署Redis数据库 6、redis管理 7、Redis数据库五大类型 8、Redis数据库基础使用 9、redis五大类型增删查?…

蓝桥备赛(四)- 数组(下)

一 、 字符数组 1.1 介绍 数组的元素如果是字符类型 &#xff0c; 这种数组就是字符数组 &#xff0c; 字符数组可以是一维数组 &#xff0c; 可以是二维数组 (多维数组)。 接下来主要讨论一维的字符数组 : char arr1[5] //一维数组 char arr2[3][5] // 二维数组 C语言 中…

数据图表ScottPlot.WPF用法示例

目录 一、添加 NuGet 程序包&#xff08;5.0.47&#xff09; 二、MainWindow.xaml中添加引用 三、MainWindow.xaml.cs 具体使用代码 图表示例&#xff1a; 一、添加 NuGet 程序包&#xff08;5.0.47&#xff09; 二、MainWindow.xaml中添加引用 <Window x:Class"…

AtCoder Beginner Contest 001(A - 積雪深差、B - 視程の通報、C - 風力観測、D - 感雨時刻の整理)题目翻译

由于我发现网上很少有人会发很久之前AtCoder Beginner Contes的题&#xff0c;所以我打算从AtCoder Beginner Contest 001开始写。大约两周一更&#xff0c;需要的可以订阅专栏&#xff0c;感谢支持Thanks♪(&#xff65;ω&#xff65;)&#xff89; →题目讲解 A - 積雪深差 …

Windows 11【1001问】查看Windows是否激活的11种方法

在使用Windows 11的过程中&#xff0c;确保系统已正确激活是非常重要的一步。未激活的系统可能会限制某些功能的使用&#xff0c;并且无法获得最新的安全更新和支持。本文将详细介绍多种判断Windows 11是否已激活的11种方法&#xff0c;帮助用户快速了解自己的系统状态&#xf…

秒杀系统的常用架构是什么?怎么设计?

架构 秒杀系统需要单独部署&#xff0c;如果说放在订单服务里面&#xff0c;秒杀的系统压力太大了就会影响正常的用户下单。 常用架构&#xff1a; Redis 数据倾斜问题 第一步扣减库存时 假设现在有 10 个商品需要秒杀&#xff0c;正常情况下&#xff0c;这 10 个商品应该均…

USRP7440-通用软件无线电平台

1、产品描述 USRP7440基于第三代XILINX Zynq UltraScale RFSoC架构&#xff0c;它将射频ADC、DAC、ARM、FPGA等集成一体&#xff0c;瞬时带宽可以达到2.5GHz&#xff0c;尤其适合于射频直采应用&#xff0c;比如通信与雷达。 第一代RFSOC高达4GHz • 8x 或 16x 6.554GSPS DAC…

【Python机器学习】1.1. 机器学习(Machine Learning)介绍

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 1.1.1. 什么是机器学习&#xff1f; 举个例子&#xff1a; 小明1月工资1000&#xff0c;每月增长10%&#xff0c;第10月是多少&#xff…

协议-Airkiss

是什么&#xff1f; 设备 A 与外界没有建立任何实质性连接&#xff0c;可以称之为信息孤岛。设备 B 通过路由 或者直接 将 Wifi 的 ssid 与密码 UDP广播 传递给 A 为什么&#xff1f; 解决将无线网络的 ssid 与密码传输到设备难题 怎么做&#xff1f; 芯片自带AT指令开启Air…

python第十一课:并发编程 | 多任务交响乐团

&#x1f3af; 本节目标 理解多线程/多进程/协程的应用场景掌握threading与multiprocessing核心用法学会使用asyncio进行异步编程开发实战项目&#xff1a;高并发爬虫引擎破解GIL锁的性能迷思 1️⃣ 并发编程三剑客 &#x1f3bb; 生活化比喻&#xff1a; 多线程 → 餐厅多个…

linux中断调用流程(arm)

文章目录 ARM架构下Linux中断处理全流程解析&#xff1a;从硬件触发到驱动调用 ⚡**一、中断触发与硬件层响应** &#x1f50c;**1. 设备触发中断** &#x1f4e1; **二、CPU阶段&#xff1a;异常入口与上下文处理** &#x1f5a5;️**1. 异常模式切换** &#x1f504;**2. 跳转…

Deepseek 模型蒸馏

赋范课堂&#xff1a; https://www.bilibili.com/video/BV1qUN8enE4c/

商城系统单商户开源版源码

环境配置 1.软件安装 宝塔安装系统软件:Nginx、MySQL5.6、PHP( PHP用7.1-7.4版本)、phpMyAdmin(Web端MySQL管理工具)。 2.配置mysql 设置mysql&#xff0c;在已安装的软件里面找到 mysql点击进行设置 3.修改sql-mode 选择左侧配置修改&#xff0c;找到里面的sql-mode&…

登录日志管理:通用分页和排序封装、 查询登录日志列表、删除登录日志、清空登录日志、解锁用户登录状态(解锁密码错误次数超限)

文章目录 引言I 登录日志管理接口列表II 通用分页和排序封装Java 分页和排序封装vue前端排序页面III 工具类字段名转换 : 驼峰转下划线命名引言 I 登录日志管理 接口列表 import request from @/utils/request// 查询登录日志列表 export function list(query) {return

Java内存管理与性能优化实践

Java内存管理与性能优化实践 Java作为一种广泛使用的编程语言&#xff0c;其内存管理和性能优化是开发者在日常工作中需要深入了解的重要内容。Java的内存管理机制借助于垃圾回收&#xff08;GC&#xff09;来自动处理内存的分配和释放&#xff0c;但要实现高效的内存管理和优…

Flutter_学习记录_实现列表上拉加载更多的功能

可以用ScrollController组件来实现这样列表上拉加载更多的功能: 1. 定义变量 在StatefulWidget 的组件内&#xff0c;添加三个属性&#xff1a; // 滚动视图的控制器final ScrollController _scrollController ScrollController();// 是否已显示了上拉加载中bool _isShowM…