当前位置: 首页 > 知识库问答 >
问题:

如何处理Redux中的复杂副作用?

方光华
2023-03-14

我已经挣扎了几个小时想找到解决这个问题的办法...

我正在开发一个在线记分牌的游戏。玩家可以随时登录和退出。完成一场比赛后,玩家会看到记分牌,看到自己的排名,分数会自动提交。

记分牌显示球员的排名,排行榜。

记分牌在用户完成游戏(提交分数)和用户只想查看他们的排名时都使用。

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

>

  • 如果用户已经登录,那么分数将首先提交。保存新记录后,记分牌将被加载。

    用户可以随时登录和退出。如果用户注销,则该用户的排名将消失,如果用户以另一个帐户登录,则该帐户的排名信息将被获取并显示。

    ...但是获取这些信息应该只发生在用户当前正在观察的记分牌上。

    对于查看操作,结果应该缓存在内存中,这样,如果用户重新订阅相同的记分牌,就不会读取。但是,如果有正在提交的分数,则不应使用缓存。

    这些网络操作中的任何一个都可能失败,玩家必须能够重试它们。

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

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

    我开始看Redux。因此,我试图构造商店,并想出了类似于这样的东西(用YAMLish语法):

    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)
    

    问题变成了:我把副作用放在哪里。

    对于无副作用的行动,这变得非常容易。例如,在logout操作中,Record简化程序可以简单地将所有记录删除。

    然而,有些行动确实有副作用。例如,如果我在提交分数之前没有登录,那么我成功登录了,set_user操作将用户保存到存储区。

    但是,因为我有一个分数要提交,这个set_user操作还必须导致触发一个AJAX请求,同时将record.leveln.status设置为loading

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

    在Elm体系结构中,当使用action->model->(Model,Effects Action)的形式时,更新程序也会产生副作用,但在Redux中,更新程序只是(State,Action)->State

    从异步操作文档中,他们推荐的方式是将它们放在操作创建者中。这是否意味着提交分数的逻辑也必须放在成功登录操作的操作创建者中?

    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)
        }
      }
    }
    

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

    1. 在减速器中。当发送set_user操作时,记录简化程序还必须将属于观察记分牌的记录的状态设置为加载
    2. 在操作创建者中,它执行获取/提交分数的实际副作用。

    似乎我还得手动跟踪所有活跃的观察者。而在bacon.js版本中,我做了如下操作:

    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))
    

    实际的Bacon代码要长得多,因为上面所有复杂的规则使得Bacon版本几乎无法读取。

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

  • 共有1个答案

    鱼阳伯
    2023-03-14

    当您需要复杂的异步依赖项时,只需使用Bacon、Rx、channels、sagas或其他异步抽象即可。你可以使用或不使用Redux。Redux示例:

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

    您可以以任何方式组合异步操作--唯一重要的部分是它们最终会变成store.dispatch(action)调用。

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

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

     类似资料:
    • 我一直在寻找如何将apache storm用作CEP的方法,但似乎有两个概念(流处理和复杂事件处理),在CEP中,您可以编写类似sql的查询,并在数据流上执行它们,如ESPER,但我在apache storm中找不到类似的东西,这是否意味着apache storm是一个数据流处理器而不是CEP?

    • 我一直试图学习如何编写树摇动友好的代码,但遇到了一个不可避免的副作用的问题,我不知道如何处理。 在我的一个模块中,我访问全局构造函数,并使用它来确定浏览器可以播放哪些音频文件(类似于现代的方式)。每当我尝试树形摇动我的代码时,元素和对它的所有引用都不会被删除,即使我没有在我的文件中导入模块。 我知道包含副作用的代码无法消除,但我找不到的是如何处理不可避免的副作用。我不能不访问全局对象来创建检测功能

    • 我有一个离散事件流进入我的系统,我需要根据每个事件的内容应用规则。另外,我想对这些流事件应用复杂的事件处理。 约束1.这些规则是用户提供的,并将动态更改。2.每当应用规则时,我不想重新启动我的系统。3.HA 4.只有成熟的开源解决方案 可能的方式...1.在Storm螺栓内运行Esper CEP 2。让口水流到Storm螺栓里 > 这会处理单事件规则和复杂事件吗?规则更改是否需要我的Storm重新

    • 我想创建一个自定义的nifi处理器,这样我就可以读取s7 plc数据。为此,我想将这个项目的java代码:https://github.com/s7connector/s7connector转换为一个nifi处理器。 因此,我已经下载了mvn包类型,就像webiste告诉的那样:https://medium.com/hashmapinc/creating-custom-processors-and

    • 我有一个复杂的endpoint,需要在spring boot中为REST服务实现。它看起来如下所示: 到此endpoint的POST将创建一个新的采购订单。段必须始终在段之前,但段可以独立存在。 采购订单

    • 我正在使用AWS Sagemaker部署在Sagemaker之外培训的语音模型。我能够将我的模型转换为Sagemaker能够理解的东西,并将其部署为endpoint。问题是Sagemaker直接加载模型并调用。预测得到推论。我无法确定在部署的模型中可以在何处添加预处理函数。建议使用AWS Lambda或其他服务器进行预处理。有没有什么方法可以将复杂的预处理(不能通过简单的Scikit、类似熊猫的框