使用ReduxToolKit

Redux可能不是一种易于理解的状态数据管理模式,虽然备受一部分人推崇,但在早期的React版本中直接使用react-redux就是一场灾难,它让无数跟风入坑的前端开发者痛苦不堪。在新版本的React中,日常开发已经大多转向函数式组件,使用Redux的正确方式是使用Redux Toolkit(后续简称RTK)框架。
RTK是基于react-redux的又一层封装,它用起来仍然充斥着不直观的逻辑和大段模板式的代码,编写这些模板代码在对Redux不熟悉时出错的概率仍然非常高,此外也并未解决使用Redux对代码调试带来的负面影响,总而言之它现在仍然非常难用,但相比早期版本已经算得上是好用一些了。我们引入RTK时一定要仔细权衡,确定我们需要它时再引入。
官方文档:https://redux-toolkit.js.org/
安装依赖
我们需要在React工程中安装以下依赖。
npm i @reduxjs/toolkit react-redux
安装调试插件
除了安装框架本体,我们还需要安装redux-devtools,它用于调试Redux相关的数据。
npm install -D redux-devtools
安装后,还需要在Chrome或Firefox浏览器中安装调试插件。

安装好后,我们就可以在浏览器的调试工具中可视化的查看Redux相关的数据了。

RTK的基本使用
这里我们以一个例子的形式来直观感受RTK的用法,我们的工程目录结构如下。
src
|_ store
|_ features
|_ counterSlice.js
|_ ...
|_ index.js
|_ App.jsx
|_ main.jsx
其中,main.jsx是React根组件,App.jsx是我们的一个自定义React组件。我们主要关注store目录,其中的index.js是RTK Store的入口,features中定义了一些Slice,counterSlice.js就是其中之一。
Slice:RTK中Slice可以理解为Store的一个片段,我们通常把一组相关的数据封装到一个Slice中。
例子代码如下,下面代码中实现了一个可以加减的计数器组件。
store/features/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
// 创建slice
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
// 导出增加和减少的action函数
export const { increment, decrement } = counterSlice.actions;
// 导出reducer
export default counterSlice.reducer;
Slice中,我们使用了createSlice()函数,它是一个用来创建Slice的工具函数,传入其中的对象内容包括数据的初始值和相关的Reducer的逻辑。最后,我们导出了创建好的Action和Reducer。
store/features/index.js
import { configureStore } from "@reduxjs/toolkit";
import counterSliceReducer from "@/store/features/counterSlice";
// 创建store
const store = configureStore({
reducer: {
counter: counterSliceReducer,
},
});
// 导出store
export default store;
index.js是RTK Store的入口,它将counterSlice相关的内容配置到Store中,并导出了store对象。
main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import store from "@/store";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
在main.jsx中,我们使用了RTK的<Provider>组件,并传入了Store对象,这样我们就可以在所有子组件中从Store读取数据了。
App.jsx
import { useDispatch, useSelector } from "react-redux";
import { increment, decrement } from "@/store/features/counterSlice";
const App = () => {
// 获取store中名为counter的slice中的数据
const value = useSelector((state) => state.counter.value);
// dispatch用于分发action
const dispatch = useDispatch();
return (
<div>
<div>{value}</div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
};
export default App;
App.jsx中,我们通过useSelector()获取名字叫counter的这个Slice的数据,并将其显示在页面上,另外我们还设置了两个按钮,用于分派increment和decrement这两个Action,分派后Store内的数据就会在Reducer的作用下改变,同时通过value再更新到页面上。
此外,Action中也可以传递参数,我们继续修改上面代码,新增一个点击一次+2的按钮。
<button onClick={() => dispatch(increment({step: 2}))}>+2</button>
代码中,我们在increment()内传递了一个对象{step: 2},在Redux中这个附加给Action的对象叫payload。
increment: (state, { payload }) => {
if (payload?.step) {
state.value += payload.step;
} else {
state.value += 1;
}
}
在counterSlice.js的increment定义中,{ payload }所在的参数对象就是被分发的Action,我们从其中读取了payload属性,并取出了它的step属性。
RTK的异步处理
看明白上面的代码后我们学习Redux就成功一半了,前面我们分发Action到Reducer处理数据是一个同步的操作,但实际开发中其实异步逻辑也有很多,例如使用FetchAPI调用接口,这里我们继续学习RTK中如何处理异步逻辑。
store/features/userSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { fetchUserList } from "@/services";
export const getUserList = createAsyncThunk(
"user/fetchUserList",
fetchUserList
);
const userSlice = createSlice({
name: "user",
initialState: {
loading: false,
total: 0,
list: [],
},
extraReducers(builder) {
builder
.addCase(getUserList.pending, (state) => {
state.loading = true;
})
.addCase(getUserList.fulfilled, (state, { payload }) => {
state.loading = false;
state.total = payload.length;
state.list = payload;
})
.addCase(getUserList.rejected, (state) => {
state.loading = false;
});
},
});
export default userSlice.reducer;
上面代码定义了一个名为user的Slice,这里我们用到了createAsyncThunk()函数,它是一个用于创建异步Action的辅助函数,它的第一个参数是Action的名字,第二个参数是具体的执行逻辑。
在createSlice()函数中,我们在参数对象中定义了extraReducers(builder) { }方法,它用于将createAsyncThunk()定义的Action整合到Slice内,这里这个异步的Action对应3种状态会分别触发三种Reducer。
App.jsx
import { useDispatch, useSelector } from "react-redux";
import { getUserList } from "@/store/features/userSlice";
const App = () => {
const { loading, total, list } = useSelector((state) => state.user);
const dispatch = useDispatch();
return (
<div>
{loading ? (
<div>Loading...</div>
) : (
<div>
<div>total: {total}</div>
<div>{JSON.stringify(list)}</div>
</div>
)}
<button onClick={() => dispatch(getUserList())}>+</button>
</div>
);
};
export default App;
在App.jsx中,读取Slice中数据的写法和之前计数器例子完全相同,唯一的不同点是这里我们点击按钮时,分发的是一个异步Action。
此时我们点击按钮,就可以看到页面上先显示“Loading...”,异步请求完成后显示对应的数据。