Redux持久化最佳实践
前言
随着 React
的不断发展,相关的集中式状态的优质解决方案逐渐增多,如: Zustand
、一系列 Redux
中间件,今天本专栏将介绍当今Redux生态中最优雅的 React
统一状态解决方案,基于 Redux-Toolkit
与 Redux_Persist
实现
Redux-Toolkit简介
Redux Toolkit 是 Redux 官方强烈推荐,开箱即用的一个高效的 Redux 开发工具集。
- 它旨在成为标准的
Redux
逻辑开发模式,我们强烈建议你使用它。 - 它包括几个实用程序功能,这些功能可以简化最常见场景下的
Redux
开发,包括配置store
、定义reducer
,不可变的更新逻辑、甚至可以立即创建整个状态的 “切片 slice”,而无需手动编写任何 action creator 或者 action type。 - 它还自带了一些最常用的
Redux
插件,例如用于异步逻辑Redux Thunk
,用于编写选择器selector
的函数Reselect
,你都可以立刻使用。
实践基础准备
其实就是下载对应的依赖
1 pnpm add antd --save
2 pnpm add redux --save
3 pnpm add react-redux --save
4 pnpm add redux-toolkit --save
5 pnpm add redux-persist --save
6
Reducer的开发
代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
1.Book 书籍reducer
在modules文件夹中的features文件夹中编写一个文件名是: bookSlice.tsx
1 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
2
3 export interface CounterState {
4 value: number;
5 bookList: Array<object>;
6 }
7 const initialState: CounterState = {
8 value: 100,
9 bookList: [
10 {
11 title: "西游记",
12 },
13 {
14 title: "水浒传",
15 },
16 {
17 title: "红楼梦",
18 },
19
20 {
21 title: "三国演义",
22 },
23 ],
24 };
25
26 const getBookListApi = () =>
27 fetch(
28 "https://mock.presstime.cn/mock/653695baffb279f23e01cefb/remoteBook"
29 ).then((res) => {
30 console.log(res);
31
32 return res.json();
33 });
34
35 // thunk函数允许执行异步逻辑, 通常用于发出异步请求。
36 // createAsyncThunk 创建一个异步action,方法触发的时候会有三种状态:
37 // pending(进行中)、fulfilled(成功)、rejected(失败)
38 export const getRemoteBookData = createAsyncThunk("book/getBook", async () => {
39 const res = await getBookListApi();
40 console.log(res);
41
42 return res;
43 });
44
45 // 创建一个 Slice
46 export const BookSlice = createSlice({
47 name: "bookData",
48 initialState,
49 // 定义 reducers 并生成关联的操作
50 reducers: {
51 // 定义一个加的方法
52 addingBook: (state, { payload, type }) => {
53 console.log(type); //bookData/addingBook
54 console.log(payload.value); //{title:"传来的值"}
55 const allList = JSON.parse(JSON.stringify(state)).bookList; //必须要重新深拷贝一次!!!
56
57 allList.push(payload.value);
58
59 state.bookList = allList;
60 },
61 },
62 // extraReducers 字段让 slice 处理在别处定义的 actions,
63 // 包括由 createAsyncThunk 或其他slice生成的actions。
64 //说白了就是判断action函数在不同状态下做什么不同的逻辑
65 extraReducers(builder) {
66 builder
67 .addCase(getRemoteBookData.pending, () => {
68 console.log("⚡ ~ 正在获取用户列表信息!");
69 })
70 .addCase(getRemoteBookData.fulfilled, (state, { payload }) => {
71 console.log("⚡ ~ 获取远程用户列表成功", payload);
72 const allList = JSON.parse(JSON.stringify(state)).bookList; //必须要重新深拷贝一次!!!
73 state.bookList = allList.concat(payload.bookList);
74 })
75 .addCase(getRemoteBookData.rejected, (_, err) => {
76 console.log("⚡ ~ 获取远程用户列表失败", err);
77 });
78 },
79 });
80 // 导出加减的方法
81 export const { addingBook } = BookSlice.actions;
82
83 // 默认导出
84 export default BookSlice.reducer;
85
86
2.User 用户reducer
在modules文件夹中的features文件夹中编写一个文件名是: userSlice.tsx
1 import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2
3 export interface MovieState {
4 list: object;
5 userList: Array<object>;
6 }
7 const initialState: MovieState = {
8 value: 100,
9 userList: [
10 {
11 title: "张明明",
12 },
13 {
14 title: "李来",
15 },
16 {
17 title: "魏韩雪",
18 },
19 ],
20 };
21
22 const getUserListApi = () =>
23 fetch(
24 "https://mock.presstime.cn/mock/653695baffb279f23e01cefb/remoteUser"
25 ).then((res) => {
26 console.log(res);
27
28 return res.json();
29 });
30
31 // thunk函数允许执行异步逻辑, 通常用于发出异步请求。
32 // createAsyncThunk 创建一个异步action,方法触发的时候会有三种状态:
33 // pending(进行中)、fulfilled(成功)、rejected(失败)
34 export const getRemoteUserData = createAsyncThunk("user/getUser", async () => {
35 const res = await getUserListApi();
36 console.log(res);
37
38 return res;
39 });
40
41 // 创建一个 Slice
42 export const UserSlice = createSlice({
43 name: "userData",
44 initialState,
45 reducers: {
46 // 数据请求完触发 loaddataend是自己写得函数名,不是内置的,叫其他名字也行
47 // addingRemoteUser: (state, { payload }) => {
48 // state.list = payload;
49 // },
50 // 定义一个加的方法
51 addingUser: (state, { payload, type }) => {
52 console.log(type); //bookData/addingBook
53 console.log(payload.value); //{title:"传来的值"}
54 const allList = JSON.parse(JSON.stringify(state)).userList; //必须要重新深拷贝一次!!!
55
56 allList.push(payload.value);
57
58 state.userList = allList;
59 },
60 },
61 // extraReducers 字段让 slice 处理在别处定义的 actions,
62 // 包括由 createAsyncThunk 或其他slice生成的actions。
63 //说白了就是判断action函数在不同状态下做什么不同的逻辑
64 extraReducers(builder) {
65 builder
66 .addCase(getRemoteUserData.pending, () => {
67 console.log("⚡ ~ 正在获取用户列表信息!");
68 })
69 .addCase(getRemoteUserData.fulfilled, (state, { payload }) => {
70 console.log("⚡ ~ 获取远程用户列表成功", payload);
71 const allList = JSON.parse(JSON.stringify(state)).userList; //必须要重新深拷贝一次!!!
72 state.userList = allList.concat(payload.userList);
73 })
74 .addCase(getRemoteUserData.rejected, (_, err) => {
75 console.log("⚡ ~ 获取远程用户列表失败", err);
76 });
77 },
78 });
79
80 // 导出方法,导出的是reducer,让外面组件使用
81 export const { addingUser } = UserSlice.actions;
82
83 // 默认导出
84 export default UserSlice.reducer;
85
86
Store仓库的开发
代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
在modules文件夹中编写一个文件名是: index.tsx
用于创建整个Redux仓库的配置
1 // index.ts 文件
2
3 import { combineReducers, configureStore } from "@reduxjs/toolkit";
4 import userSlice from "./features/userSlice";
5 import bookSlice from "./features/bookSlice";
6 import { persistStore, persistReducer } from "redux-persist";
7 import storage from "redux-persist/es/storage";
8
9 const persistConfig = {
10 key: "root",
11 storage,
12 blacklist: ["userSlice"], //一般的黑名单,只能达到一级的禁止,要想实现更深层级的禁止或允许持久化,可使用嵌套持久化实现,下面就是
13 };
14
15 const userPersistConfig = {
16 key: "user",
17 storage,
18 blacklist: ["value"],
19 }; //实现嵌套持久化,重写user的持久化配置
20
21 const reducers = combineReducers({
22 userSlice: persistReducer(userPersistConfig, userSlice), //实现嵌套持久化,原理是在localstorage中再开辟一个空间专门存储user相关的数据,在user里面在限制黑名单即可,这样子就实现了仅仅黑名单(user里面的value数据)
23 bookSlice: bookSlice,
24 });
25
26 const persistedReducer = persistReducer(persistConfig, reducers);
27
28 // configureStore创建一个redux数据仓库
29 const store = configureStore({
30 // 合并多个Slice
31 reducer: persistedReducer,
32 });
33
34 //创建一个redux持久化仓库
35 export const persistor = persistStore(store);
36
37 export default store;
38
39
Redux配置
代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
在整个项目的入口文件 main.tsx
中进行如下代码编写
1 import React from "react";
2 import ReactDOM from "react-dom/client";
3 import { Provider } from "react-redux";
4 import { PersistGate } from "redux-persist/integration/react";
5 import App from "./App.tsx";
6 import store, { persistor } from "./modules/index.tsx";
7
8 ReactDOM.createRoot(document.getElementById("root")!).render(
9 <React.StrictMode>
10 <Provider store={store}>
11 <PersistGate loading={null} persistor={persistor}>
12 <App />
13 </PersistGate>
14 </Provider>
15 </React.StrictMode>
16 );
17
18
组件调用状态与操作
- 代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
- 由于本次实践是一个小案例的形式,所以引入了antd进行简要的UI开发
在整个项目的src文件夹中的文件 app.tsx
中进行如下代码编写
1 import {
2 Input,
3 Avatar,
4 List,
5 Button,
6 Popconfirm,
7 Divider,
8 Select,
9 message,
10 } from "antd";
11 import { useState } from "react";
12 import { useDispatch, useSelector } from "react-redux";
13 import { addingBook, getRemoteBookData } from "./modules/features/bookSlice";
14 import { addingUser, getRemoteUserData } from "./modules/features/userSlice";
15
16 const { Option } = Select;
17
18 function App() {
19 const dispatch = useDispatch();
20
21 const [iptValue, setIptValue] = useState<string>("");
22 const [typeValue, setTypeValue] = useState<string>("book");
23 const { userList } = useSelector((store: any) => store.userSlice);
24 const { bookList } = useSelector((store: any) => store.bookSlice);
25
26 const handleAddingData = () => {
27 if (iptValue !== "") {
28 const preObject: any = { title: iptValue };
29 if (typeValue === "user") {
30 dispatch(addingUser({ value: preObject })); //这里要求,必须专递对象数据,reducer的payload来接
31 message.success("用户添加成功"); //简便起见,就这么加了,实际应该放在操作完成之后
32 } else if (typeValue === "book") {
33 dispatch(addingBook({ value: preObject })); //这里要求,必须专递对象数据,reducer的payload来接
34 message.success("书籍添加成功"); //简便起见,就这么加了,实际应该放在操作完成之后
35 }
36 }
37 };
38
39 const handleAddingRemoteUser = () => {
40 dispatch(getRemoteUserData());
41 message.success("获取远程用户列表成功"); //简便起见,就这么加了,实际应该放在操作完成之后
42 };
43
44 const handleAddingRemoteBook = () => {
45 dispatch(getRemoteBookData());
46 message.success("获取远程用户列表成功"); //简便起见,就这么加了,实际应该放在操作完成之后
47 };
48
49 return (
50 <>
51 <div style={{ display: "flex" }}>
52 <div
53 style={{
54 width: "670px",
55 display: "flex",
56 flexDirection: "column",
57 margin: "0 auto",
58 marginTop: "10px",
59 }}
60 >
61 <div style={{ marginBottom: "40px", display: "flex" }}>
62 <Input
63 addonAfter={
64 <Select
65 defaultValue="book"
66 onChange={(value) => {
67 setTypeValue(value);
68 }}
69 >
70 <Option value="book">书籍信息</Option>
71 <Option value="user">用户信息</Option>
72 </Select>
73 }
74 placeholder="输入一些相关的数据吧"
75 size="large"
76 onChange={(value) => {
77 setIptValue(value.target.value);
78 }}
79 />
80 <Button
81 size="large"
82 style={{ marginLeft: "10px" }}
83 type="primary"
84 onClick={() => {
85 handleAddingData();
86 }}
87 >
88 提交
89 </Button>
90 </div>
91
92 <div
93 style={{
94 display: "flex",
95 alignItems: "center",
96 justifyContent: "flex-end",
97 }}
98 >
99 <Button
100 type="primary"
101 style={{ marginRight: "30px" }}
102 onClick={() => {
103 handleAddingRemoteUser();
104 }}
105 >
106 远程用户列表
107 </Button>
108 <Button
109 type="primary"
110 onClick={() => {
111 handleAddingRemoteBook();
112 }}
113 >
114 远程书籍列表
115 </Button>
116 </div>
117
118 <Divider>用户列表</Divider>
119
120 <div>
121 <List
122 itemLayout="horizontal"
123 dataSource={userList}
124 renderItem={(item, index) => (
125 <List.Item
126 actions={[
127 <Popconfirm
128 title="删除日程确认"
129 description="你确定要删除这个日程?"
130 okText="确认"
131 cancelText="取消"
132 >
133 <Button danger type="primary" size="small">
134 删除用户
135 </Button>
136 </Popconfirm>,
137 ]}
138 >
139 <List.Item.Meta
140 avatar={
141 <Avatar
142 src={`https://xsgames.co/randomusers/avatar.php?g=pixel&key=${index}`}
143 />
144 }
145 title={<a href="https://ant.design">{item.title}</a>}
146 description="Ant Design, a design language for background applications, is refined by Ant UED Team"
147 />
148 </List.Item>
149 )}
150 />
151 </div>
152
153 <Divider>书籍列表</Divider>
154
155 <div>
156 <List
157 itemLayout="horizontal"
158 dataSource={bookList}
159 renderItem={(item, index) => (
160 <List.Item
161 actions={[
162 <Popconfirm
163 title="删除日程确认"
164 description="你确定要删除这个日程?"
165 okText="确认"
166 cancelText="取消"
167 >
168 <Button danger type="primary" size="small">
169 删除书籍
170 </Button>
171 </Popconfirm>,
172 ]}
173 >
174 <List.Item.Meta
175 avatar={
176 <Avatar
177 src={`https://xsgames.co/randomusers/avatar.php?g=pixel&key=${index}`}
178 />
179 }
180 title={<a href="https://ant.design">{item.title}</a>}
181 description="Ant Design, a design language for background applications, is refined by Ant UED Team"
182 />
183 </List.Item>
184 )}
185 />
186 </div>
187 </div>
188 </div>
189 </>
190 );
191 }
192
193 export default App;
194
195
总结
本实践没有过多的文本描述,多在代码中的注释。但通过此个实践了解学习之后,应该可以较好的掌握Redux-Toolkit和Redux-Persist这套优雅的React全局状态管理方案
上一篇:
npm、pnpm、yarn之间的区别
下一篇:
如何封装更简单易用的命令式组件?
相关笔记