主页 > 创业  > 

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

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: 仅供参考

标签:

vite+react+ts如何集成redux状态管理工具,实现持久化缓存由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“vite+react+ts如何集成redux状态管理工具,实现持久化缓存