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
,你都可以立刻使用。
实践基础准备
其实就是下载对应的依赖
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>
);
组件调用状态与操作
- 代码里面都有详细的注释,结合注释对于了解React与Redux的完全可以看懂
- 由于本次实践是一个小案例的形式,所以引入了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全局状态管理方案