如何处理Redux中复杂的副作用? [英] How to handle complex side-effects in Redux?

查看:94
本文介绍了如何处理Redux中复杂的副作用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在努力寻找解决这个问题的方法......

I've been struggling for hours to finding a solution to this problem...

我正在开发一款带有在线记分牌的游戏。玩家可以随时登录和注销。完成游戏后,玩家将看到记分牌,并查看自己的排名,并且分数将自动提交。

I am developing a game with an online scoreboard. The player can log in and log out at any time. After finishing a game, the player will see the scoreboard, and see their own rank, and the score will be submitted automatically.

记分牌显示玩家的排名,以及排行榜。

The scoreboard shows the player’s ranking, and the leaderboard.

记分牌在用户完成比赛时使用(提交分数)当用户只想查看他们的排名时。

The scoreboard is used both when the user finishes playing (to submit a score), and when the user just wants to check out their ranking.

这是逻辑变得非常复杂的地方:

This is where the logic becomes very complicated:


  • 如果用户已登录,则会先提交分数。保存新记录后,将加载记分板。

  • If the user is logged in, then the score will be submitted first. After the new record is saved then the scoreboard will be loaded.

否则,记分板将立即加载。玩家将获得登录或注册的选项。之后,分数将被提交,然后记分板将再次刷新。

Otherwise, the scoreboard will be loaded immediately. The player will be given an option to log in or register. After that, the score will be submitted, and then the scoreboard will be refreshed again.

但是,如果没有分数要提交(只需查看高分)得分表)。在这种情况下,只需下载播放器的现有记录。但由于此操作不会影响记分牌,因此记分牌和玩家记录应同时下载。

However, if there is no score to submit (just viewing the high score table). In this case, the player’s existing record is simply downloaded. But since this action does not affect the scoreboard, both the scoreboard and the player’s record should be downloaded simultaneously.

级别数量不限。每个级别都有不同的记分牌。当用户查看记分板时,用户正在观察该记分板。当它关闭时,用户停止观察它。

There is an unlimited number of levels. Each level has a different scoreboard. When the user views a scoreboard, then the user is ‘observing’ that scoreboard. When it is closed, the user stops observing it.

用户可以随时登录和注销。如果用户注销,则用户的排名应该消失,如果用户以其他帐户登录,则应该获取并显示该帐户的排名信息。

The user can log in and log out at any time. If the user logs out, the user’s ranking should disappear, and if the user logs in as another account, then the ranking information for that account should be fetched and displayed.

...但是,此信息只能用于当前用户正在观察的记分板。

...but this fetching this information should only take place for the scoreboard whose user is currently observing.

对于查看操作,结果应缓存在 - 内存,因此如果用户重新订阅同一个记分板,则不会进行提取。但是,如果提交了分数,则不应使用缓存。

For viewing operations, the results should be cached in-memory, so that if user re-subscribes to the same scoreboard, there will be no fetching. However, if there is a score being submitted, the cache should not be used.

任何这些网络操作都可能失败,并且播放器必须能够重试它们。

Any of these network operations may fail, and the player must be able to retry them.

这些操作应该是原子的。所有州都应该一次更新(没有中间状态)。

These operations should be atomic. All the states should be updated in one go (no intermediate states).

目前,我能够解决这个问题。使用Bacon.js(功能反应式编程库),因为它带有原子更新支持。代码非常简洁,但现在它是一个混乱的不可预测的意大利面条代码。

Currently, I am able to solve this using Bacon.js (a functional reactive programming library), as it comes with atomic update support. The code is quite concise, but right now it is a messy unpredictable spaghetti code.

我开始关注Redux。所以我尝试构建商店,并想出了类似的东西(用YAMLish语法):

I started looking at Redux. So I tried to structure the store, and came up with something like this (in YAMLish syntax):

user: (user information)
record:
  level1:
    status: (loading / completed / error)
    data:   (record data)
    error:  (error / null)
scoreboard:
  level1:
    status: (loading / completed / error)
    data:
      - (record data)
      - (record data)
      - (record data)
    error:  (error / null)

问题变为:我是否会产生副作用。

The problem becomes: where do I put the side-effects.

对于无副作用的动作,这变得非常容易。例如,在 LOGOUT 操作中,记录 reducer可以简单地关闭所有记录。

For side-effect-free actions, this becomes very easy. For instance, on LOGOUT action, the record reducer could simply blast all the records off.

但是,某些操作确实有副作用。例如,如果我在提交分数之前没有登录,那么我成功登录, SET_USER 操作会将用户保存到商店中。

However, some actions do have side effect. For example, if I am not logged in before submitting the score, then I log in successfully, the SET_USER action saves the user into the store.

但是因为我有一个要提交的分数,这个 SET_USER 动作也必须引发一个AJAX请求,同时,将 record.levelN.status 设置为 loading

But because I have a score to submit, this SET_USER action must also cause an AJAX request to be fired off, and at the same time, set the record.levelN.status to loading.

问题是:当我以原子方式登录时,如何表示副作用(得分提交)应该发生?

The question is: how do I signify that a side-effects (score submission) should take place when I log in in an atomic way?

在Elm架构中,当使用 Action - >形式时,更新程序也会发出副作用。型号 - > (模型,效果动作),但在Redux中,它只是(状态,动作) - >州

In Elm architecture, an updater can also emit side-effects when using the form of Action -> Model -> (Model, Effects Action), but in Redux, it’s just (State, Action) -> State.

来自 Async Actions 文档,他们推荐的方式是将它们放入动作创建者中。这是否意味着提交分数的逻辑也必须放在动作创建者中以进行成功的登录操作?

From the Async Actions docs, the way they recommend is to put them in an action creator. Does this means that the logic of submitting the score will have to be put in the action creator for a successful login action as well?

function login (options) {
  return (dispatch) => {
    service.login(options).then(user => dispatch(setUser(user)))
  }
}

function setUser (user) {
  return (dispatch, getState) => {
    dispatch({ type: 'SET_USER', user })
    let scoreboards = getObservedScoreboards(getState())
    for (let scoreboard of scoreboards) {
      service.loadUserRanking(scoreboard.level)
    }
  }
}

我发现这有点奇怪,因为负责这种连锁反应的代码现在存在于两个地方:

I find this a bit odd, because the code responsible for this chain reaction now exists in 2 places:


  1. 在reducer中。当调度 SET_USER 操作时,记录 reducer还必须将属于观察记分板的记录的状态设置为 loading

  2. 在动作创建器中,它执行获取/提交分数的实际副作用。

  1. In the reducer. When SET_USER action is dispatched, the record reducer must also set the status of the records belonging to the observed scoreboards to loading.
  2. In the action creator, which performs the actual side-effect of fetching/submitting score.

似乎我必须手动跟踪所有活跃的观察者。而在Bacon.js版本中,我做了类似这样的事情:

It also seems that I have to manually keep track of all the active observers. Whereas in Bacon.js version, I did something like this:

Bacon.once() // When first observing the scoreboard
.merge(resubmit口) // When resubmitting because of network error
.merge(user川.changes().filter(user => !!user).first()) // When user logs in (but only once)
.flatMapLatest(submitOrGetRanking(data))

实际由于上面所有复杂的规则,培根代码要长得多,这使得培根版本几乎无法读取。

The actual Bacon code is a lot longer, because of the all the complex rules above, that made the Bacon version barely-readable.

但培根会自动跟踪所有有效订阅。这让我开始质疑它可能不值得切换,因为将其重写为Redux需要大量的手动处理。任何人都可以建议一些指针吗?

But Bacon kept track of all active subscriptions automatically. This led me to start questioning that it might not be worth the switch, because rewriting this to Redux would require a lot of manual handling. Can anyone suggest some pointer?

推荐答案

当你想要复杂的异步依赖时,只需使用培根,Rx,频道,传奇或其他异步抽象。您可以使用或不使用Redux。 Redux示例:

When you want complex async dependencies, just use Bacon, Rx, channels, sagas, or another asynchronous abstraction. You can use them with or without Redux. Example with Redux:

observeSomething()
  .flatMap(someTransformation)
  .filter(someFilter)
  .map(createActionSomehow)
  .subscribe(store.dispatch);

您可以按照自己喜欢的方式编写异步操作 - 唯一重要的部分是他们最终会变成 store.dispatch(action) calls。

You can compose your asynchronous actions any way you like—the only important part is that eventually they turn into store.dispatch(action) calls.

Redux Thunk 对于简单的应用程序来说已经足够了,但是当您的异步需求变得更加复杂时,您需要使用真正的异步组合抽象,而Redux并不关心您使用哪一个。

Redux Thunk is enough for simple apps, but as your async needs get more sophisticated, you need to use a real asynchronous composition abstraction, and Redux doesn't care which one you use.

更新:一段时间过去了,出现了一些新的解决方案。我建议你查看 Redux Saga ,它已经成为Redux中异步控制流程的一个相当流行的解决方案。

Update: Some time has passed, and a few new solutions have emerged. I suggest you to check out Redux Saga which has become a fairly popular solution for async control flow in Redux.

这篇关于如何处理Redux中复杂的副作用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆