异步模式

Redux 异步模式

Redux Store 本身并不了解任何关于异步逻辑的东西。它只知道如何同步调度动作,通过调用根减速函数更新状态,并通知 UI 有变化。任何异步性都必须发生在 Store 之外。但是,如果你想让异步逻辑通过调度或检查当前存储状态与存储进行交互呢?这就是 Redux 中间件的作用。它们扩展了存储,并允许你:

  • 当任何动作被派发时,执行额外的逻辑(如记录动作和状态)。
  • 暂停、修改、延迟、替换或停止被派发的动作。
  • 编写能够访问 dispatch 和 getState 的额外代码。
  • 教会 dispatch 如何接受除了普通动作对象以外的其他值,比如函数和 Promise,通过拦截它们并调度真正的动作对象来代替它们

Redux 的异步中间件有很多种,每种中间件都可以让你使用不同的语法来编写逻辑。最常见的异步中间件有:

  • redux-thunk,让你可以直接写出可能包含异步逻辑的普通函数。
  • redux-saga,它使用生成器函数来返回行为描述,以便它们可以被中间件执行。
  • redux-observable,它使用 RxJS observable 库来创建处理动作的函数链。

我们推荐使用 Redux Thunk 中间件作为标准方法,因为它足以满足大多数典型用例(如基本的 AJAX 数据获取)。此外,在 thunks 中使用 async/await 语法使其更容易阅读。

在 Slices 中定义异步逻辑

Redux Toolkit 目前没有提供任何特殊的 API 或语法来编写 thunk 函数。特别是,它们不能被定义为 createSlice()调用的一部分。你必须把它们从 reducer 逻辑中分离出来,就像写普通 Redux 代码一样。Thunks 通常调度普通的动作,如 dispatch(dataLoaded(response.data))。许多 Redux 应用程序使用 folder-by-type 的方法来构建他们的代码。在这种结构中,thunk action creators 通常与普通 action creators 一起定义在 actions 文件中。因为我们没有单独的 actions 文件,所以直接将这些 thunk 写入我们的 “分片 “文件是有意义的。这样一来,他们就可以从分片中访问普通 action creators,而且很容易找到 thunk 函数的位置。

一个典型的包含 thunks 的分片文件会是这样的。

// First, define the reducer and action creators via `createSlice`
const usersSlice = createSlice({
  name: "users",
  initialState: {
    loading: "idle",
    users: [],
  },
  reducers: {
    usersLoading(state, action) {
      // Use a "state machine" approach for loading state instead of booleans
      if (state.loading === "idle") {
        state.loading = "pending";
      }
    },
    usersReceived(state, action) {
      if (state.loading === "pending") {
        state.loading = "idle";
        state.users = action.payload;
      }
    },
  },
});

// Destructure and export the plain action creators
export const { usersLoading, usersReceived } = usersSlice.actions;

// Define a thunk that dispatches those action creators
const fetchUsers = () => async (dispatch) => {
  dispatch(usersLoading());
  const response = await usersAPI.fetchAll();
  dispatch(usersReceived(response.data));
};

Redux 的数据获取逻辑通常遵循一种可预测的模式。

  • 在请求之前,先派发一个 “start” 动作,以表明请求正在进行中。这可以用来跟踪加载状态,以允许跳过重复的请求或在 UI 中显示加载指标。
  • 发出实际请求
  • 根据请求结果,异步逻辑会派发一个包含结果数据的 “success” 操作,或一个包含错误详情的 “failure” 操作。在这两种情况下,reducer 都会清除加载状态,并处理成功情况下的结果数据,或者存储错误值,以便潜在地显示。

作为开发人员,你可能最关心的是提出 API 请求所需的实际逻辑,Redux 动作历史日志中显示的动作类型名称,以及你的 reducers 应该如何处理获取的数据。定义多个动作类型和以正确的顺序调度动作的重复性细节并不重要。createAsyncThunk 简化了这一过程–你只需要为动作类型前缀提供一个字符串,并提供一个 payload 创建者回调,它可以完成实际的异步逻辑并返回一个带有结果的承诺。作为回报,createAsyncThunk 会给你一个 thunk,它将根据你返回的 promise,以及你可以在 reducers 中处理的动作类型,负责调度正确的动作。

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { userAPI } from "./userAPI";

// First, create the thunk
const fetchUserById = createAsyncThunk(
  "users/fetchByIdStatus",
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId);
    return response.data;
  }
);

// Then, handle actions in your reducers:
const usersSlice = createSlice({
  name: "users",
  initialState: { entities: [], loading: "idle" },
  reducers: {
    // standard reducer logic, with auto-generated action types per reducer
  },
  extraReducers: {
    // Add reducers for additional action types here, and handle loading state as needed
    [fetchUserById.fulfilled]: (state, action) => {
      // Add user to the state array
      state.entities.push(action.payload);
    },
  },
});

// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123));

thunk 动作创建者接受一个参数,它将作为第一个参数传递给你的 payload 创建者回调。payload 创建者还将收到一个 thunkAPI 对象,其中包含通常传递给标准 Redux thunk 函数的参数,以及一个自动生成的唯一随机请求 ID 字符串和一个 AbortController.signal 对象。

interface ThunkAPI {
  dispatch: Function
  getState: Function
  extra?: any
  requestId: string
  signal: AbortSignal
}
上一页