2022年2月19日

了解redux-saga:解决移动应用程序的状态管理及异步编程

作者 admin

对于任何redux 开发人员,构建应用程序最困难的部分是异步调用 — 如何在不使 redux actions和reducers复杂化的情况下处理网络请求、超时和其他回调?

为了管理这种复杂性,我将介绍一些在应用中处理异步的不同方法,从简单的方法(如 redux-thunk)到功能更强大的库(如 redux-saga)。

我们将使用 React 和 Redux,所以这篇文章假设你至少对它们有一些熟悉。

Action Creaters

调用 API 是许多应用中的常见要求。想象一下,当我们点击一个按钮时,我们需要显示一张随机图片:

我们可以使用API和在ActionCreater内部一个Fetch调用这样简单的东西:

这种方法没有错。在所有条件相同的情况下,我们应该采取最简单的方法。

但是,单独使用 Redux 不会给我们带来太多的灵活性。Redux 的核心只是一个支持同步数据流的状态容器。

每次将操作(描述所发生情况的普通对象)发送到store时,都会调用reducer并立即更新状态。

但在异步流中,必须首先等待响应,然后,如果没有错误,则更新状态。如果您的应用程序具有复杂的业务逻辑或流程,该怎么办?

Redux使用中间件来解决此问题。中间件是在调度操作之后但在到达reducer之前执行的一段代码。

许多中间件可以安排成一个执行链,以不同的方式处理操作。但是,中间件必须解释您传递给它的任何内容,并且必须确保在链的末端调度一个普通对象(操作)。

对于异步操作,Redux 提供了 redux-thunk 中间件。

Redux-thunk

Redux-thunk是一个常用的处理方式在Redux中。

我们的目的, a thunk表示不会被立即执行的函数, 仅仅在需要的时候执行. 可参考redux-thunk的例子: redux-thunk’s documentation:

值3被立即赋给X.

然而,像下面这行代码:

sum 操作不会立即执行,仅在调用 foo() 时才会执行。这使得foo成为一个thunk。

Redux-thunk允许action creator除了普通对象之外调度一个函数,将acion creator转换为thunk。

乍一看,这似乎与以前的方法没有太大区别。

没有 redux-thunk:

使用 redux-thunk:

使用 redux-thunk 的优点是组件不知道它正在执行异步操作。

由于中间件会自动将dispatch函数传递给action creator返回的函数,因此对于组件,请求执行同步操作和异步操作之间没有区别(无论如何,它们都不必关心)。

通过使用中间件,我们添加了一个间接层,为我们提供了更大的灵活性。

由于 redux-thunk 将存储中的 dispatch 和 getState 方法作为参数提供给dispatch函数,因此您还可以调度其他操作并读取状态以实现更复杂的业务逻辑和工作流。

另一个好处是,如果某些内容太复杂而无法用 thunks 表示,则无需更改组件,我们可以使用另一个中间件库来进行更多控制。

让我们看看怎么替换redux-thunk提供给我们更大的灵活性, redux-saga.

Redux-saga

Redux-saga是一个库,旨在通过使用sages使附加effect更容易,更好。

Sagas 是一种来自分布式事务世界的设计模式,其中 saga 管理需要以事务方式执行的流程,保持执行状态并补偿失败的流程。

在 Redux 的上下文中,saga 作为中间件实现(我们不能使用reducer,因为这必须是一个纯函数)来协调和触发异步操作(副加effects)。

Redux-saga在ES6生成器的帮助下做到这一点:

Generator是可以暂停和恢复的函数,而不是一次性执行函数的所有语句.

调用generator函数时,它将返回迭代器对象。每次调用迭代器的 next() 方法时,gernerator的主体将被执行,直到下一个 yield 语句,然后暂停:

这可以使异步代码易于编写和理解。例如,而不是这样做:

使用generator,我们可以这样做:

回到redux-saga,我们通常有一个saga,其工作是监视调度的操作:

为了协调我们要在 saga 中实现的逻辑,我们可以使用像 takeEvery 这样的helper程序函数来生成一个新的 saga 来执行操作:

如果有多个请求,takeEvery 将启动工作线程 saga 的多个实例。换句话说,它为您处理并发性。

请注意,watcher saga 是另一层间接层,它为实现复杂逻辑提供了更大的灵活性(但对于简单的应用来说可能是不必要的)。

现在,我们可以像这样实现 fetchDogAsync() 函数(假设我们有权访问dispatch方法):

但是 redux-saga 允许我们生成一个对象,该对象声明我们执行操作的意图,而不是生成执行操作本身的结果。换句话说,上面的示例是以这种方式在 redux-saga 中实现的:

方法call将仅返回描述操作的普通对象,而不是直接调用异步请求,以便 redux-saga 可以处理调用并将结果返回给生成器。

put 方法也会发生同样的事情。put 不是在生成器内调度操作,而是返回一个对象,其中包含中间件调度操作的指令。

这些返回的对象称为“Effects”。下面是call方法返回的effect的示例:

通过使用 Effects,redux-saga 使 sagas 成为声明性的,而不是命令性的

声明性编程是一种编程风格,它试图通过描述程序必须完成的内容而不是描述如何完成它来最小化或消除附加effect。

这带来的好处(也是大多数人讨论过的)是,返回简单对象的函数比直接进行异步调用的函数更容易测试。要运行测试,您不需要使用真正的API,伪造它或模拟它。

对于测试,您只需循环访问生成器函数,断言生成的值相等:

但是,另一个额外的好处是能够轻松地将许多effects组合到复杂的工作流程中。

除了 takeEverycall 和 put 之外,redux-saga 还为限制获取当前状态并行运行任务取消任务提供了许多effect creators,仅举几例。

当您单击该按钮时,将发生以下情况:

  1. 调度操作FETCHED_DOG
  2. 观察程序 saga (watchFetchDog) 执行调度的操作并调用 worker saga (fetchDogAsync)
  3. 调度显示加载指示器的操作
  4. 执行 API 调用
  5. 调度更新状态的操作(成功或失败)

如果你认为一些间接层和一点点额外的工作是值得的,那么redux-saga可以给你更多的控制权,以一种功能性的方式处理附加effects。

结论

这篇文章展示了如何在Redux中实现具有action creaters,thunks和sagas的异步操作,从最简单的方法到最复杂的方法。

Redux没有规定处理附加effect的解决方案。在决定采用哪种方法时,必须考虑应用程序的复杂性。我的建议是从最简单的解决方案开始。

Redux-saga还有其他值得尝试的替代品。两个最受欢迎的选项是 redux-observable(基于 RxJS)和 redux-logic(也基于 RxJS observables,但可以自由地用其他样式编写逻辑)。