react-redux 搭配 @reduxjs/toolkit 处理异步请求

4,429 阅读2分钟

一、前言

思路:利用@reduxjs/toolkit提供的createAsyncThunk接口,搭配createSlice接口中的extraReducers选项使用,实现React框架下的异步请求处理后相关Store中的state同步更新。

createAsyncThunk.gif

图片来源: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文件

image.png

  • @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 新增用户,异步请求后端接口,并同步更新usersStorestate

  • 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