一、前言
思路:利用@reduxjs/toolkit提供的createAsyncThunk接口,搭配createSlice接口中的extraReducers选项使用,实现React框架下的异步请求处理后相关Store中的state同步更新。
图片来源:Redux官网文档
推荐视频:B站英文教程视频
代码仓库:链接
二、开发环境准备
2.1 新建React工程
npx create-vite react-redux-async --template react
2.2 安装工具包
pnpm install
# 安装处理异步请求的工具包
pnpm install react-redux @reduxjs/toolkit axios
# 安装mock服务的工具包
pnpm install -D json-server @faker-js/faker
2.3 搭建mock服务
src/mock/db.js
const { faker } = require('@faker-js/faker')
const { nanoid } = require('@reduxjs/toolkit')
module.exports = () => {
const usersList = []
const productList = []
for (let i = 1; i <= 5; i++) {
usersList.push({
id: nanoid(),
name: faker.name.firstName(),
email: faker.internet.email(),
sex: faker.name.sexType(),
})
productList.push({
id: nanoid(),
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
})
}
return { users: usersList, product: productList }
}
package.json
{
"name": "react-redux-async",
"private": true,
"version": "0.0.0",
"scripts": {
// 添加mock服务相关的脚本命令
"mock": "json-server --watch src/mock/db.js --port 3001"
},
}
src/utils/http.js
import axios from 'axios'
const http = axios.create({
baseURL: 'http://127.0.0.1:3001',
timeout: 5000,
})
export { http }
三、核心步骤实现
3.1 创建一个Redux Store
- 在
src目录下创建store目录,及index.js文件
- 从
@reduxjs/toolkit工具包导入configureStoreAPI,创建一个空的Redux Store并将其导出
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {},
})
- 向React项目提供
Redux Store
// src/main.jsx
import store from './app/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
3.2 创建一个Redux状态切片
- 在
src/store/modules/usersSlice.js中,利用createSliceAPI新建一个名为users的状态切片
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
users: [],
status: 'idle',
error: null,
}
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
})
export default usersSlice.reducer
- 在
src/store/index.js中添加上一步新建的usersStore
import usersStore from './modules/usersSlice'
export default configureStore({
reducer: { usersStore },
})
3.3 createAsyncThunk搭配createSlice中的extraReducers选项,实现异步请求处理
extraReducers选项是一个函数,它接收一个builder参数。builder对象提供了一些方法,可定义额外的case reducers,用于处理由异步thunks调度的action,并更新Redux Store中的State。createAsyncThunk用于定义异步thunks调度的action
// src/store/modules/usersSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { http } from '@/utils/http'
// 获取用户信息列表
export const fetchUsers = createAsyncThunk('fetchUsers', async () => {
const res = await http.get('/users')
return res.data
})
// 新增用户
export const addUser = createAsyncThunk('addUser', async (params) => {
const res = await http.post('/users', params)
return res.data
})
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchUsers.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded'
state.users = action.payload
})
.addCase(addUser.fulfilled, (state, action) => {
state.status = 'successed'
state.users.push(action.payload)
})
},
})
// 提供给useSelector使用,用于获取users列表
export const selectAllUsers = (state) => state.usersStore.users
export default usersSlice.reducer
3.4 React前端首次渲染时,异步加载users列表
src/main.jsx
import store from './store'
import { fetchUsers } from '@/store/modules/usersSlice'
// 首次渲染时,异步请求加载用户列表信息,从而初始化usersStore的users列表
store.dispatch(fetchUsers())
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
)
src/App.jsx
import { useSelector } from 'react-redux'
import { selectAllUsers } from '@/store/modules/usersSlice'
function App() {
const users = useSelector(selectAllUsers)
return (
<div className="App">
<ul>
// 用户列表信息展示
{users.map((item) => (
<li key={item.id}>
{item.name} / {item.email} / {item.sex}
</li>
))}
</ul>
</div>
)
}
export default App
3.5 新增用户,异步请求后端接口,并同步更新usersStore的state
src/App.jsx
import { useDispatch, useSelector } from 'react-redux'
import { nanoid } from '@reduxjs/toolkit'
import { useState } from 'react'
import { addUser, selectAllUsers } from '@/store/modules/usersSlice'
function App() {
const dispatch = useDispatch()
const users = useSelector(selectAllUsers)
const [username, setUsername] = useState('')
const [email, setEmail] = useState('')
const [sex, setSex] = useState('')
const clickHandler = async (e) => {
e.preventDefault()
// 异步请求,新增用户信息
await dispatch(
addUser({ id: nanoid(), name: username, email: email, sex: sex })
)
setUsername('')
setEmail('')
setSex('')
}
return (
<div className="App">
<form>
<input
name="username"
placeholder="Please input username!"
value={username}
onChange={(e) => {
setUsername(e.target.value)
}}
/>
<input
name="email"
placeholder="Please input email!"
value={email}
onChange={(e) => {
setEmail(e.target.value)
}}
/>
<select name="sex" value={sex} onChange={(e) => setSex(e.target.value)}>
<option value="">请选择性别</option>
<option value="male">male</option>
<option value="female">female</option>
</select>
<button onClick={clickHandler}>提交</button>
</form>
<hr />
<ul>
{users.map((item) => (
<li key={item.id}>
{item.name} / {item.email} / {item.sex}
</li>
))}
</ul>
</div>
)
}
export default App