首页 > web前端 > js教程 > React Redux应用中的异步操作

React Redux应用中的异步操作

Lisa Kudrow
发布: 2025-02-16 12:02:12
原创
495 人浏览过

Async Operations in React Redux Applications

关键要点

  • JavaScript 的单线程特性意味着异步操作(如 API 调用)对于非阻塞式 UI 至关重要,高效处理这些操作在 React Redux 应用中至关重要。
  • Redux Thunk、Redux-Saga 和 Redux-Observables 是流行的 Redux 异步操作管理中间件,它们各自根据应用的复杂性和需求提供不同的优势。
  • Redux Thunk 通过允许返回函数而不是 action 来简化异步 action 分发,从而能够在这些函数中进行顺序 API 调用和处理相关数据。
  • Redux-Saga 利用 ES6 生成器为复杂场景(如处理竞争条件、暂停和取消操作)提供更强大的解决方案,使其适合大型应用。
  • Redux-Observables 利用 RxJS 提供了一种强大的方法来通过 action 流管理副作用,在复杂的应用程序架构中提供出色的可扩展性和对异步操作的控制。

此文章最初发表于 Codebrahma。

JavaScript 是一种单线程编程语言。也就是说,当你编写如下代码时……

Async Operations in React Redux Applications

……第二行只有在第一行执行完毕后才会执行。大多数情况下这不会有问题,因为客户端或服务器每秒执行数百万次计算。只有当我们执行代价高昂的计算(需要相当长的时间才能完成的任务——网络请求需要一些时间才能返回)时,我们才会注意到这些影响。

为什么我只在这里展示 API 调用(网络请求)?其他异步操作呢? API 调用是一个非常简单且有用的例子,用于描述如何处理异步操作。还有其他操作,例如 setTimeout()、性能密集型计算、图像加载和任何事件驱动的操作。

在构建应用程序时,我们需要考虑异步执行如何影响结构。例如,将 fetch() 视为从浏览器执行 API 调用(网络请求)的函数。(忽略它是否是 AJAX 请求。只需将其行为视为异步或同步。)请求在服务器上处理时经过的时间不会发生在主线程上。因此,您的 JS 代码将继续执行,一旦请求返回响应,它将更新线程。

考虑这段代码:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登录后复制
登录后复制
登录后复制
登录后复制

在这种情况下,由于 fetch() 是异步的,当我们尝试获取 userDetails 时,我们将不会拥有 userId。因此,我们需要以一种确保第二行仅在第一行返回响应后才执行的方式来构建它。

大多数现代网络请求实现都是异步的。但这并不总是有效,因为我们依赖于先前 API 响应数据来进行后续 API 调用。让我们看看如何在 ReactJS/Redux 应用程序中特别构建它。

React 是一个用于创建用户界面的前端库。Redux 是一个状态容器,可以管理应用程序的整个状态。将 React 与 Redux 结合使用,我们可以创建高效且可扩展的应用程序。在这样的 React 应用程序中,有多种方法可以构建异步操作。对于每种方法,我们将讨论其在以下因素方面的优缺点:

  • 代码清晰度
  • 可扩展性
  • 易于错误处理

对于每种方法,我们将执行这两个 API 调用:

1. 从 userDetails(第一个 API 响应)中获取 city

假设端点是 /details。它将在响应中包含城市。响应将是一个对象:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登录后复制
登录后复制
登录后复制
登录后复制

2. 基于用户 city,我们将获取该 city 中的所有餐厅

假设端点是 /restuarants/:city。响应将是一个数组:

userDetails: {
  …
  city: 'city',
  …
};
登录后复制
登录后复制

请记住,我们只有在完成第一个请求后才能发出第二个请求(因为它依赖于第一个请求)。让我们看看各种方法:

  • 直接使用 Promise 或 async/await 与 setState
  • 使用 Redux Thunk
  • 使用 Redux-Saga
  • 使用 Redux Observables

我特别选择了上述方法,因为它们是大型项目中最常用的方法。仍然存在其他方法可能更特定于特定任务,并且不具备复杂应用程序所需的所有功能(例如 redux-async、redux-promise、redux-async-queue)。

Promises

Promise 是一个对象,它可能在将来的某个时间产生单个值:已解析的值或未解析的原因(例如,发生网络错误)。— Eric Elliot

在我们的例子中,我们将使用 axios 库来获取数据,当我们发出网络请求时,它会返回一个 Promise。该 Promise 可能会解析并返回响应或抛出错误。因此,一旦 React 组件 挂载,我们可以直接像这样获取:

['restaurant1', 'restaurant2', …]
登录后复制

通过这种方式,当状态发生变化(由于获取)时,组件 将自动重新渲染并加载餐厅列表。

Async/await 是一种新的实现,我们可以用它来进行异步操作。例如,可以通过以下方式实现相同的功能:

componentDidMount() {
  axios.get('/details') // 获取用户详细信息
    .then(response => {
      const userCity = response.city;
      axios.get(`/restaurants/${userCity}`)
        .then(restaurantResponse => {
          this.setState({
            listOfRestaurants: restaurantResponse, // 设置状态
          })
        })
    })
}
登录后复制

这两种方法都是最简单的。由于整个逻辑都在组件内部,因此我们可以很容易地在组件加载后一次性获取所有数据。

此方法的缺点

当进行基于数据的复杂交互时,问题就会出现。例如,考虑以下情况:

Async Operations in React Redux Applications

  • 我们不希望执行 JS 的线程被网络请求阻塞。
  • 所有上述情况都会使代码变得非常复杂,难以维护和测试。
  • 此外,可扩展性将是一个大问题,因为如果我们计划更改应用程序的流程,我们需要从组件中删除所有获取操作。
  • 想象一下,如果组件位于父-子树的顶部,我们会怎么做。然后我们需要更改所有依赖于数据的表示组件。
  • 还要注意,整个业务逻辑都在组件内部。

我们如何改进?

  1. 状态管理 在这些情况下,使用全局存储实际上可以解决我们一半的问题。我们将使用 Redux 作为我们的全局存储。

  2. 将业务逻辑移动到正确的位置 如果我们考虑将业务逻辑移出组件,那么我们究竟可以在哪里这样做?在 actions 中?在 reducers 中?通过中间件?Redux 的架构是同步的。一旦你分发一个 action(JS 对象)并且它到达存储,reducer 就会对其进行操作。

  3. 确保有一个单独的线程执行异步代码,并且可以通过订阅检索对全局状态的任何更改

Async Operations in React Redux Applications

由此,我们可以了解到,如果我们将所有获取逻辑移动到 reducer 之前——即 action 或中间件——那么就可以在正确的时间分发正确的 action。例如,一旦获取开始,我们可以分发 dispatch({ type: 'FETCH_STARTED' }),当它完成时,我们可以分发 dispatch({ type: 'FETCH_SUCCESS' })

想要开发一个 React JS 应用程序?

使用 Redux Thunk

Redux Thunk 是 Redux 的中间件。它基本上允许我们返回函数而不是对象作为 action。这通过提供 dispatchgetState 作为函数的参数来提供帮助。我们有效地使用 dispatch 在正确的时间分发必要的 actions。好处是:

  • 允许在函数内进行多次分发
  • 业务逻辑与获取的关联将从 React 组件移到 actions 中。

在我们的例子中,我们可以像这样重写 action:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登录后复制
登录后复制
登录后复制
登录后复制

如你所见,我们现在可以很好地控制何时分发哪种类型的 action。每个函数调用(如 fetchStarted()fetchUserDetailsSuccess()fetchRestaurantsSuccess()fetchError())都会分发一个类型为普通 JavaScript 对象的 action,如果需要,还可以添加其他详细信息。因此,现在 reducer 的任务是处理每个 action 并更新视图。我没有讨论 reducer,因为它从这里开始很简单,并且实现可能会有所不同。

为了使这能够工作,我们需要将 React 组件与 Redux 连接起来,并使用 Redux 库将 action 与组件绑定。一旦完成,我们可以简单地调用 this.props.getRestaurants(),这反过来将处理所有上述任务并根据 reducer 更新视图。

就其可扩展性而言,Redux Thunk 可用于不涉及对异步 action 进行复杂控制的应用程序。此外,它与其他库无缝协同工作,如下一节主题中所述。

但是,使用 Redux Thunk 来执行某些任务仍然有点困难。例如,我们需要暂停中间的获取操作,或者当有多个这样的调用时,只允许最新的调用,或者如果其他 API 获取此数据并且我们需要取消。

我们仍然可以实现这些,但是精确地执行会比较复杂。与其他库相比,复杂任务的代码清晰度会略差,并且维护起来会比较困难。

使用 Redux-Saga

使用 Redux-Saga 中间件,我们可以获得额外的优势来解决上述大多数功能。Redux-Saga 是基于 ES6 生成器开发的。

Redux-Saga 提供了一个 API,有助于实现以下目标:

  • 阻塞事件,在同一行阻塞线程,直到完成某些操作
  • 非阻塞事件,使代码异步
  • 处理多个异步请求之间的竞争
  • 暂停/节流/去抖动任何 action

Saga 如何工作?

Saga 使用 ES6 生成器和 async/await API 的组合来简化异步操作。它基本上在其单独的线程上执行其工作,我们可以在其中进行多个 API 调用。我们可以使用它们的 API 使每个调用同步或异步,具体取决于用例。API 提供了使线程在同一行等待直到请求返回响应的功能。除此之外,此库还提供了许多其他 API,这使得 API 请求非常易于处理。

考虑我们之前的示例:如果我们初始化一个 saga 并根据其文档中提到的内容将其与 Redux 配置,我们可以执行以下操作:

userDetails: {
  …
  city: 'city',
  …
};
登录后复制
登录后复制

因此,如果我们使用类型为 FETCH_RESTAURANTS 的简单 action 进行分发,Saga 中间件将侦听并响应。实际上,没有一个 action 被中间件使用。它只是侦听并执行一些其他任务,如果需要,则分发新的 action。通过使用此架构,我们可以分发多个请求,每个请求描述:

  • 第一个请求何时开始
  • 第一个请求何时完成
  • 第二个请求何时开始

等等。

此外,您可以看到 fetchRestaurantSaga() 的优点。我们目前使用 call API 来实现阻塞调用。Saga 提供其他 API,例如 fork(),它实现非阻塞调用。我们可以组合阻塞和非阻塞调用来维护适合我们应用程序的结构。

就可扩展性而言,使用 saga 有益:

  • 我们可以根据任何特定任务来构建和分组 saga。我们可以通过简单地分发 action 来从另一个 saga 触发一个 saga。
  • 由于它是中间件,我们编写的 action 将是普通的 JS 对象,与 thunk 不同。
  • 由于我们将业务逻辑放在 saga 内部(这是一个中间件),如果我们知道 saga 的功能是什么,那么理解它的 React 部分就会容易得多。
  • 可以通过 try/catch 模式轻松监控错误并将其分发到存储。

使用 Redux-Observables

正如其文档中“Epic 是 redux-observable 的核心原语”部分所述:

  1. Epic 是一个函数,它接收 action 流并返回 action 流。也就是说,Epic 与正常的 Redux 分发通道并行运行,在 reducer 已经接收它们之后。
  2. action 始终在 epic 接收它们之前通过 reducer 运行。Epic 只接收并输出另一个 action 流。这与 Redux-Saga 类似,因为没有一个 action 被中间件使用。它只是侦听并执行一些其他任务。

对于我们的任务,我们可以简单地编写如下代码:

userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId
userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
登录后复制
登录后复制
登录后复制
登录后复制

起初,这可能看起来有点令人困惑。但是,你越了解 RxJS,创建 Epic 就越容易。

与 saga 一样,我们可以分发多个 action,每个 action 都描述线程当前位于 API 请求链的哪个部分。

就可扩展性而言,我们可以根据特定任务拆分 Epic 或组合 Epic。因此,此库可以帮助构建可扩展的应用程序。如果我们理解编写代码的可观察模式,则代码清晰度很好。

我的偏好

如何确定使用哪个库?这取决于我们的 API 请求有多复杂。

如何选择 Redux-Saga 和 Redux-Observable 之间?这取决于学习生成器还是 RxJS。两者都是不同的概念,但同样足够好。我建议尝试两者,看看哪个更适合你。

将处理 API 的业务逻辑放在哪里?最好放在 reducer 之前,但不要放在组件中。最好的方法是在中间件中(使用 saga 或 observable)。

您可以在 Codebrahma 阅读更多 React 开发文章。

关于 React-Redux 应用程序中异步操作的常见问题

中间件在处理 Redux 中的异步操作中的作用是什么?

Redux 中的中间件在处理异步操作中起着至关重要的作用。它在分发 action 和 action 到达 reducer 之间提供了一个第三方扩展点。中间件可用于记录、修改甚至取消 action,以及分发其他 action。在异步操作的上下文中,像 Redux Thunk 或 Redux Saga 这样的中间件允许您编写返回函数而不是 action 的 action 创建者。然后,可以使用此函数来延迟 action 的分发或仅在满足特定条件时分发 action。

Redux Thunk 如何帮助管理异步操作?

Redux Thunk 是一个中间件,允许您编写返回函数而不是 action 的 action 创建者。thunk 可用于延迟 action 的分发或仅在满足特定条件时分发 action。此功能使其成为处理 Redux 中异步操作的绝佳工具。例如,您可以分发一个 action 来指示 API 调用的开始,然后在调用返回数据或错误消息时分发另一个 action。

Redux Thunk 和 Redux Saga 之间的区别是什么?

Redux Thunk 和 Redux Saga 都是用于管理 Redux 中副作用(包括异步操作)的中间件。两者之间的主要区别在于它们的方法。Redux Thunk 使用回调函数来处理异步操作,而 Redux Saga 使用生成器函数和更声明式的方法。这使得 Redux Saga 更加强大和灵活,但也更加复杂。如果您的应用程序具有简单的异步操作,Redux Thunk 可能就足够了。但是,对于涉及竞争条件、取消和 if-else 逻辑的更复杂场景,Redux Saga 可能是更好的选择。

如何在 Redux 中处理异步操作中的错误?

可以在异步操作期间发生错误时分发 action 来处理 Redux 中异步操作中的错误处理。此 action 可以将其错误消息作为其有效负载。然后,您可以在 reducer 中处理此 action 以使用错误消息更新状态。这样,可以向用户显示错误消息或将其记录以进行调试。

如何在 Redux 中测试异步 action?

可以通过模拟 Redux 存储和 API 调用来测试 Redux 中的异步 action。对于 Redux 存储,您可以使用像 redux-mock-store 这样的库。对于 API 调用,您可以使用像 fetch-mocknock 这样的库。在您的测试中,您可以分发异步 action,然后断言已使用正确的有效负载分发了预期的 action。

如何在 Redux 中取消异步操作?

可以使用像 Redux Saga 这样的中间件来取消 Redux 中的异步操作。Redux Saga 使用生成器函数,可以使用 cancel effect 来取消这些函数。当 cancel effect 被 yield 时,saga 将从其启动点到当前 effect 被取消。

如何在 Redux 中处理异步操作中的竞争条件?

可以使用像 Redux Saga 这样的中间件来处理 Redux 中异步操作中的竞争条件。Redux Saga 提供了像 takeLatesttakeEvery 这样的 effect,可用于处理并发 action。例如,如果在分发新 action 时,先前启动的 saga 任务仍在运行,则 takeLatest 会取消该任务。

如何将 async/await 与 Redux Thunk 一起使用?

Redux Thunk 原生支持 async/await。在您的 action 创建者中,您可以返回异步函数而不是常规函数。在此异步函数内部,您可以使用 async/await 来处理异步操作。当异步操作完成时,可以使用 action 对象调用 dispatch 函数。

如何在 Redux 中的异步操作中处理加载状态?

可以通过在异步操作之前和之后分发 action 来处理 Redux 中异步操作中的加载状态。在操作之前分发的 action 可以将加载状态设置为 true,而操作之后分发的 action 可以将其设置为 false。在您的 reducer 中,您可以处理这些 action 以更新存储中的加载状态。

如何在 Redux 中处理副作用?

可以使用像 Redux Thunk 或 Redux Saga 这样的中间件来处理 Redux 中的副作用。这些中间件允许您编写返回函数而不是 action 的 action 创建者。此函数可用于执行副作用,例如异步操作、记录和有条件地分发 action。

以上是React Redux应用中的异步操作的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板