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全局状态管理方案

相关推荐
轻口味27 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami30 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235951 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
麦兜*2 小时前
轮播图带详情插件、uniApp插件
前端·javascript·uni-app·vue