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