关键要点
此文章最初发表于 Codebrahma。
JavaScript 是一种单线程编程语言。也就是说,当你编写如下代码时……
……第二行只有在第一行执行完毕后才会执行。大多数情况下这不会有问题,因为客户端或服务器每秒执行数百万次计算。只有当我们执行代价高昂的计算(需要相当长的时间才能完成的任务——网络请求需要一些时间才能返回)时,我们才会注意到这些影响。
为什么我只在这里展示 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 调用:
userDetails
(第一个 API 响应)中获取 city假设端点是 /details
。它将在响应中包含城市。响应将是一个对象:
userId = fetch(userEndPoint); // 从 userEndpoint 获取 userId userDetails = fetch(userEndpoint, userId) // 为此特定 userId 获取数据。
假设端点是 /restuarants/:city
。响应将是一个数组:
userDetails: { … city: 'city', … };
请记住,我们只有在完成第一个请求后才能发出第二个请求(因为它依赖于第一个请求)。让我们看看各种方法:
setState
我特别选择了上述方法,因为它们是大型项目中最常用的方法。仍然存在其他方法可能更特定于特定任务,并且不具备复杂应用程序所需的所有功能(例如 redux-async、redux-promise、redux-async-queue)。
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, // 设置状态 }) }) }) }
这两种方法都是最简单的。由于整个逻辑都在组件内部,因此我们可以很容易地在组件加载后一次性获取所有数据。
当进行基于数据的复杂交互时,问题就会出现。例如,考虑以下情况:
状态管理 在这些情况下,使用全局存储实际上可以解决我们一半的问题。我们将使用 Redux 作为我们的全局存储。
将业务逻辑移动到正确的位置 如果我们考虑将业务逻辑移出组件,那么我们究竟可以在哪里这样做?在 actions 中?在 reducers 中?通过中间件?Redux 的架构是同步的。一旦你分发一个 action(JS 对象)并且它到达存储,reducer 就会对其进行操作。
确保有一个单独的线程执行异步代码,并且可以通过订阅检索对全局状态的任何更改
由此,我们可以了解到,如果我们将所有获取逻辑移动到 reducer 之前——即 action 或中间件——那么就可以在正确的时间分发正确的 action。例如,一旦获取开始,我们可以分发 dispatch({ type: 'FETCH_STARTED' })
,当它完成时,我们可以分发 dispatch({ type: 'FETCH_SUCCESS' })
。
想要开发一个 React JS 应用程序?
Redux Thunk 是 Redux 的中间件。它基本上允许我们返回函数而不是对象作为 action。这通过提供 dispatch
和 getState
作为函数的参数来提供帮助。我们有效地使用 dispatch
在正确的时间分发必要的 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 是基于 ES6 生成器开发的。
Redux-Saga 提供了一个 API,有助于实现以下目标:
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 有益:
正如其文档中“Epic 是 redux-observable 的核心原语”部分所述:
对于我们的任务,我们可以简单地编写如下代码:
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 开发文章。
Redux 中的中间件在处理异步操作中起着至关重要的作用。它在分发 action 和 action 到达 reducer 之间提供了一个第三方扩展点。中间件可用于记录、修改甚至取消 action,以及分发其他 action。在异步操作的上下文中,像 Redux Thunk 或 Redux Saga 这样的中间件允许您编写返回函数而不是 action 的 action 创建者。然后,可以使用此函数来延迟 action 的分发或仅在满足特定条件时分发 action。
Redux Thunk 是一个中间件,允许您编写返回函数而不是 action 的 action 创建者。thunk 可用于延迟 action 的分发或仅在满足特定条件时分发 action。此功能使其成为处理 Redux 中异步操作的绝佳工具。例如,您可以分发一个 action 来指示 API 调用的开始,然后在调用返回数据或错误消息时分发另一个 action。
Redux Thunk 和 Redux Saga 都是用于管理 Redux 中副作用(包括异步操作)的中间件。两者之间的主要区别在于它们的方法。Redux Thunk 使用回调函数来处理异步操作,而 Redux Saga 使用生成器函数和更声明式的方法。这使得 Redux Saga 更加强大和灵活,但也更加复杂。如果您的应用程序具有简单的异步操作,Redux Thunk 可能就足够了。但是,对于涉及竞争条件、取消和 if-else 逻辑的更复杂场景,Redux Saga 可能是更好的选择。
可以在异步操作期间发生错误时分发 action 来处理 Redux 中异步操作中的错误处理。此 action 可以将其错误消息作为其有效负载。然后,您可以在 reducer 中处理此 action 以使用错误消息更新状态。这样,可以向用户显示错误消息或将其记录以进行调试。
可以通过模拟 Redux 存储和 API 调用来测试 Redux 中的异步 action。对于 Redux 存储,您可以使用像 redux-mock-store
这样的库。对于 API 调用,您可以使用像 fetch-mock
或 nock
这样的库。在您的测试中,您可以分发异步 action,然后断言已使用正确的有效负载分发了预期的 action。
可以使用像 Redux Saga 这样的中间件来取消 Redux 中的异步操作。Redux Saga 使用生成器函数,可以使用 cancel
effect 来取消这些函数。当 cancel
effect 被 yield 时,saga 将从其启动点到当前 effect 被取消。
可以使用像 Redux Saga 这样的中间件来处理 Redux 中异步操作中的竞争条件。Redux Saga 提供了像 takeLatest
和 takeEvery
这样的 effect,可用于处理并发 action。例如,如果在分发新 action 时,先前启动的 saga 任务仍在运行,则 takeLatest
会取消该任务。
Redux Thunk 原生支持 async/await。在您的 action 创建者中,您可以返回异步函数而不是常规函数。在此异步函数内部,您可以使用 async/await 来处理异步操作。当异步操作完成时,可以使用 action 对象调用 dispatch
函数。
可以通过在异步操作之前和之后分发 action 来处理 Redux 中异步操作中的加载状态。在操作之前分发的 action 可以将加载状态设置为 true,而操作之后分发的 action 可以将其设置为 false。在您的 reducer 中,您可以处理这些 action 以更新存储中的加载状态。
可以使用像 Redux Thunk 或 Redux Saga 这样的中间件来处理 Redux 中的副作用。这些中间件允许您编写返回函数而不是 action 的 action 创建者。此函数可用于执行副作用,例如异步操作、记录和有条件地分发 action。
以上是React Redux应用中的异步操作的详细内容。更多信息请关注PHP中文网其他相关文章!