Redux持久化全局状态管理最佳实践

Redux持久化最佳实践

前言

随着React的不断发展,相关的集中式状态的优质解决方案逐渐增多,如:Zustand、一系列Redux中间件,今天本专栏将介绍当今Redux生态中最优雅的React统一状态解决方案,基于Redux-ToolkitRedux_Persist实现

Redux-Toolkit简介

Redux Toolkit 是 Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集

  • 它旨在成为标准的 Redux 逻辑开发模式,我们强烈建议你使用它。
  • 它包括几个实用程序功能,这些功能可以简化最常见场景下的 Redux开发,包括配置 store、定义reducer,不可变的更新逻辑、甚至可以立即创建整个状态的 "切片 slice",而无需手动编写任何 action creator 或者 action type。
  • 它还自带了一些最常用的 Redux 插件,例如用于异步逻辑 Redux Thunk,用于编写选择器 selector的函数 Reselect ,你都可以立刻使用。

实践基础准备

其实就是下载对应的依赖

shell 复制代码
 pnpm add antd --save
 pnpm add redux --save
 pnpm add react-redux --save
 pnpm add redux-toolkit --save
 pnpm add redux-persist --save

Reducer的开发

代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂

1.Book 书籍reducer

在modules文件夹中的features文件夹中编写一个文件名是:bookSlice.tsx

tsx 复制代码
 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
 ​
 export interface CounterState {
     value: number;
     bookList: Array<object>;
 }
 const initialState: CounterState = {
     value: 100,
     bookList: [
         {
             title: "西游记",
         },
         {
             title: "水浒传",
         },
         {
             title: "红楼梦",
         },
 ​
         {
             title: "三国演义",
         },
     ],
 };
 ​
 const getBookListApi = () =>
     fetch(
         "https://mock.presstime.cn/mock/653695baffb279f23e01cefb/remoteBook"
     ).then((res) => {
         console.log(res);
 ​
         return res.json();
     });
 ​
 // thunk函数允许执行异步逻辑, 通常用于发出异步请求。
 // createAsyncThunk 创建一个异步action,方法触发的时候会有三种状态:
 // pending(进行中)、fulfilled(成功)、rejected(失败)
 export const getRemoteBookData = createAsyncThunk("book/getBook", async () => {
     const res = await getBookListApi();
     console.log(res);
 ​
     return res;
 });
 ​
 // 创建一个 Slice
 export const BookSlice = createSlice({
     name: "bookData",
     initialState,
     // 定义 reducers 并生成关联的操作
     reducers: {
         // 定义一个加的方法
         addingBook: (state, { payload, type }) => {
             console.log(type); //bookData/addingBook
             console.log(payload.value); //{title:"传来的值"}
             const allList = JSON.parse(JSON.stringify(state)).bookList; //必须要重新深拷贝一次!!!
 ​
             allList.push(payload.value);
 ​
             state.bookList = allList;
         },
     },
     // extraReducers 字段让 slice 处理在别处定义的 actions,
     // 包括由 createAsyncThunk 或其他slice生成的actions。
     //说白了就是判断action函数在不同状态下做什么不同的逻辑
     extraReducers(builder) {
         builder
             .addCase(getRemoteBookData.pending, () => {
                 console.log("⚡ ~ 正在获取用户列表信息!");
             })
             .addCase(getRemoteBookData.fulfilled, (state, { payload }) => {
                 console.log("⚡ ~ 获取远程用户列表成功", payload);
                 const allList = JSON.parse(JSON.stringify(state)).bookList; //必须要重新深拷贝一次!!!
                 state.bookList = allList.concat(payload.bookList);
             })
             .addCase(getRemoteBookData.rejected, (_, err) => {
                 console.log("⚡ ~ 获取远程用户列表失败", err);
             });
     },
 });
 // 导出加减的方法
 export const { addingBook } = BookSlice.actions;
 ​
 // 默认导出
 export default BookSlice.reducer;
 ​

2.User 用户reducer

在modules文件夹中的features文件夹中编写一个文件名是:userSlice.tsx

tsx 复制代码
 import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
 ​
 export interface MovieState {
     list: object;
     userList: Array<object>;
 }
 const initialState: MovieState = {
     value: 100,
     userList: [
         {
             title: "张明明",
         },
         {
             title: "李来",
         },
         {
             title: "魏韩雪",
         },
     ],
 };
 ​
 const getUserListApi = () =>
     fetch(
         "https://mock.presstime.cn/mock/653695baffb279f23e01cefb/remoteUser"
     ).then((res) => {
         console.log(res);
 ​
         return res.json();
     });
 ​
 // thunk函数允许执行异步逻辑, 通常用于发出异步请求。
 // createAsyncThunk 创建一个异步action,方法触发的时候会有三种状态:
 // pending(进行中)、fulfilled(成功)、rejected(失败)
 export const getRemoteUserData = createAsyncThunk("user/getUser", async () => {
     const res = await getUserListApi();
     console.log(res);
 ​
     return res;
 });
 ​
 // 创建一个 Slice
 export const UserSlice = createSlice({
     name: "userData",
     initialState,
     reducers: {
         // 数据请求完触发 loaddataend是自己写得函数名,不是内置的,叫其他名字也行
         // addingRemoteUser: (state, { payload }) => {
         //  state.list = payload;
         // },
         // 定义一个加的方法
         addingUser: (state, { payload, type }) => {
             console.log(type); //bookData/addingBook
             console.log(payload.value); //{title:"传来的值"}
             const allList = JSON.parse(JSON.stringify(state)).userList; //必须要重新深拷贝一次!!!
 ​
             allList.push(payload.value);
 ​
             state.userList = allList;
         },
     },
     // extraReducers 字段让 slice 处理在别处定义的 actions,
     // 包括由 createAsyncThunk 或其他slice生成的actions。
     //说白了就是判断action函数在不同状态下做什么不同的逻辑
     extraReducers(builder) {
         builder
             .addCase(getRemoteUserData.pending, () => {
                 console.log("⚡ ~ 正在获取用户列表信息!");
             })
             .addCase(getRemoteUserData.fulfilled, (state, { payload }) => {
                 console.log("⚡ ~ 获取远程用户列表成功", payload);
                 const allList = JSON.parse(JSON.stringify(state)).userList; //必须要重新深拷贝一次!!!
                 state.userList = allList.concat(payload.userList);
             })
             .addCase(getRemoteUserData.rejected, (_, err) => {
                 console.log("⚡ ~ 获取远程用户列表失败", err);
             });
     },
 });
 ​
 // 导出方法,导出的是reducer,让外面组件使用
 export const { addingUser } = UserSlice.actions;
 ​
 // 默认导出
 export default UserSlice.reducer;
 ​

Store仓库的开发

代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂

在modules文件夹中编写一个文件名是:index.tsx用于创建整个Redux仓库的配置

tsx 复制代码
 // index.ts 文件
 ​
 import { combineReducers, configureStore } from "@reduxjs/toolkit";
 import userSlice from "./features/userSlice";
 import bookSlice from "./features/bookSlice";
 import { persistStore, persistReducer } from "redux-persist";
 import storage from "redux-persist/es/storage";
 ​
 const persistConfig = {
     key: "root",
     storage,
     blacklist: ["userSlice"], //一般的黑名单,只能达到一级的禁止,要想实现更深层级的禁止或允许持久化,可使用嵌套持久化实现,下面就是
 };
 ​
 const userPersistConfig = {
     key: "user",
     storage,
     blacklist: ["value"],
 }; //实现嵌套持久化,重写user的持久化配置
 ​
 const reducers = combineReducers({
     userSlice: persistReducer(userPersistConfig, userSlice), //实现嵌套持久化,原理是在localstorage中再开辟一个空间专门存储user相关的数据,在user里面在限制黑名单即可,这样子就实现了仅仅黑名单(user里面的value数据)
     bookSlice: bookSlice,
 });
 ​
 const persistedReducer = persistReducer(persistConfig, reducers);
 ​
 // configureStore创建一个redux数据仓库
 const store = configureStore({
     // 合并多个Slice
     reducer: persistedReducer,
 });
 ​
 //创建一个redux持久化仓库
 export const persistor = persistStore(store);
 ​
 export default store;
 ​

Redux配置

代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂

在整个项目的入口文件main.tsx中进行如下代码编写

tsx 复制代码
 import React from "react";
 import ReactDOM from "react-dom/client";
 import { Provider } from "react-redux";
 import { PersistGate } from "redux-persist/integration/react";
 import App from "./App.tsx";
 import store, { persistor } from "./modules/index.tsx";
 ​
 ReactDOM.createRoot(document.getElementById("root")!).render(
     <React.StrictMode>
         <Provider store={store}>
             <PersistGate loading={null} persistor={persistor}>
                 <App />
             </PersistGate>
         </Provider>
     </React.StrictMode>
 );
 ​

组件调用状态与操作

  1. 代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
  2. 由于本次实践是一个小案例的形式,所以引入了antd进行简要的UI开发

在整个项目的src文件夹中的文件app.tsx中进行如下代码编写

tsx 复制代码
 import {
     Input,
     Avatar,
     List,
     Button,
     Popconfirm,
     Divider,
     Select,
     message,
 } from "antd";
 import { useState } from "react";
 import { useDispatch, useSelector } from "react-redux";
 import { addingBook, getRemoteBookData } from "./modules/features/bookSlice";
 import { addingUser, getRemoteUserData } from "./modules/features/userSlice";
 ​
 const { Option } = Select;
 ​
 function App() {
     const dispatch = useDispatch();
 ​
     const [iptValue, setIptValue] = useState<string>("");
     const [typeValue, setTypeValue] = useState<string>("book");
     const { userList } = useSelector((store: any) => store.userSlice);
     const { bookList } = useSelector((store: any) => store.bookSlice);
 ​
     const handleAddingData = () => {
         if (iptValue !== "") {
             const preObject: any = { title: iptValue };
             if (typeValue === "user") {
                 dispatch(addingUser({ value: preObject })); //这里要求,必须专递对象数据,reducer的payload来接
                 message.success("用户添加成功"); //简便起见,就这么加了,实际应该放在操作完成之后
             } else if (typeValue === "book") {
                 dispatch(addingBook({ value: preObject })); //这里要求,必须专递对象数据,reducer的payload来接
                 message.success("书籍添加成功"); //简便起见,就这么加了,实际应该放在操作完成之后
             }
         }
     };
 ​
     const handleAddingRemoteUser = () => {
         dispatch(getRemoteUserData());
         message.success("获取远程用户列表成功"); //简便起见,就这么加了,实际应该放在操作完成之后
     };
 ​
     const handleAddingRemoteBook = () => {
         dispatch(getRemoteBookData());
         message.success("获取远程用户列表成功"); //简便起见,就这么加了,实际应该放在操作完成之后
     };
 ​
     return (
         <>
             <div style={{ display: "flex" }}>
                 <div
                     style={{
                         width: "670px",
                         display: "flex",
                         flexDirection: "column",
                         margin: "0 auto",
                         marginTop: "10px",
                     }}
                 >
                     <div style={{ marginBottom: "40px", display: "flex" }}>
                         <Input
                             addonAfter={
                                 <Select
                                     defaultValue="book"
                                     onChange={(value) => {
                                         setTypeValue(value);
                                     }}
                                 >
                                     <Option value="book">书籍信息</Option>
                                     <Option value="user">用户信息</Option>
                                 </Select>
                             }
                             placeholder="输入一些相关的数据吧"
                             size="large"
                             onChange={(value) => {
                                 setIptValue(value.target.value);
                             }}
                         />
                         <Button
                             size="large"
                             style={{ marginLeft: "10px" }}
                             type="primary"
                             onClick={() => {
                                 handleAddingData();
                             }}
                         >
                             提交
                         </Button>
                     </div>
 ​
                     <div
                         style={{
                             display: "flex",
                             alignItems: "center",
                             justifyContent: "flex-end",
                         }}
                     >
                         <Button
                             type="primary"
                             style={{ marginRight: "30px" }}
                             onClick={() => {
                                 handleAddingRemoteUser();
                             }}
                         >
                             远程用户列表
                         </Button>
                         <Button
                             type="primary"
                             onClick={() => {
                                 handleAddingRemoteBook();
                             }}
                         >
                             远程书籍列表
                         </Button>
                     </div>
 ​
                     <Divider>用户列表</Divider>
 ​
                     <div>
                         <List
                             itemLayout="horizontal"
                             dataSource={userList}
                             renderItem={(item, index) => (
                                 <List.Item
                                     actions={[
                                         <Popconfirm
                                             title="删除日程确认"
                                             description="你确定要删除这个日程?"
                                             okText="确认"
                                             cancelText="取消"
                                         >
                                             <Button danger type="primary" size="small">
                                                 删除用户
                                             </Button>
                                         </Popconfirm>,
                                     ]}
                                 >
                                     <List.Item.Meta
                                         avatar={
                                             <Avatar
                                                 src={`https://xsgames.co/randomusers/avatar.php?g=pixel&key=${index}`}
                                             />
                                         }
                                         title={<a href="https://ant.design">{item.title}</a>}
                                         description="Ant Design, a design language for background applications, is refined by Ant UED Team"
                                     />
                                 </List.Item>
                             )}
                         />
                     </div>
 ​
                     <Divider>书籍列表</Divider>
 ​
                     <div>
                         <List
                             itemLayout="horizontal"
                             dataSource={bookList}
                             renderItem={(item, index) => (
                                 <List.Item
                                     actions={[
                                         <Popconfirm
                                             title="删除日程确认"
                                             description="你确定要删除这个日程?"
                                             okText="确认"
                                             cancelText="取消"
                                         >
                                             <Button danger type="primary" size="small">
                                                 删除书籍
                                             </Button>
                                         </Popconfirm>,
                                     ]}
                                 >
                                     <List.Item.Meta
                                         avatar={
                                             <Avatar
                                                 src={`https://xsgames.co/randomusers/avatar.php?g=pixel&key=${index}`}
                                             />
                                         }
                                         title={<a href="https://ant.design">{item.title}</a>}
                                         description="Ant Design, a design language for background applications, is refined by Ant UED Team"
                                     />
                                 </List.Item>
                             )}
                         />
                     </div>
                 </div>
             </div>
         </>
     );
 }
 ​
 export default App;
 ​

总结

本实践没有过多的文本描述,多在代码中的注释。但通过此个实践了解学习之后,应该可以较好的掌握Redux-Toolkit和Redux-Persist这套优雅的React全局状态管理方案

相关推荐
C澒2 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅4 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘6 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端