【问题标题】:How do you manage expensive derived calculations in a "flux" application如何在“通量”应用程序中管理昂贵的派生计算
【发布时间】:2015-02-02 06:47:06
【问题描述】:

我目前正在使用通常与 ReactJS 相关的通量模式开发原型应用程序。

在 Facebook flux/chat example 中,有两个商店,ThreadStoreUnreadThreadStore。后者提供了一个同步读取前者内容的getAll方法。

我们遇到了一个问题,即派生存储中的操作太昂贵而无法同步执行,理想情况下会委托给异步进程(网络工作者、服务器跳闸),我们想知道如何解决这个问题.

我的同事建议从 getter 那里返回一个承诺,即

# MyView

componentDidMount: function () {
    defaultState = { results: [] };
    this.setState(defaultState);
    DerivedStore.doExpensiveThing()
        .then(this.setState);
}

我对此并不完全满意。感觉就像是打破了模式,因为视图是变化的主要接受者,而不是商店。这是我们一直在探索的另一种途径 - 在该途径中,视图安装事件会发送对派生数据进行刷新的需求(如果需要)。

 # DerivedStore
 # =========================================================
 state: {
     derivedResults: []
     status: empty <fresh|pending|empty>
 },
 handleViewAction: function (payload) {
    if (payload.type === "refreshDerivedData") {
        this.state.status = "pending"; # assume an async action has started
    }
    if (payload.type === "derivedDataIsRefreshed") {
        this.state.status = "fresh"; # the async action has completed
    }
    this.state.derivedResults = payload.results || []
    this.notify();
 }

 # MyAction
 # =========================================================
 MyAction = function (dispatcher) {
    dispatcher.register(function (payload) {
        switch (payload) {
            case "refreshDerivedData": 
               doExpensiveCalculation()
                   .then(function(res) {
                        dispatcher.dispatch({
                            type: "derivedDataIsRefreshed",
                            results: res
                        })
                    })
               );
        }
    });
 };

 # MyView
 # =========================================================
 MyView = React.createClass({
     componentDidMount: function () {
         if (DerivedStore.getState().status === "empty") {
             Dispatcher.dispatch("refreshDerivedData");
         }
     },
     getVisibility: function () {
         return DerivedStore.getState().status === "pending" ? "is-visible" : ""
     },
     render: function () {
         var state = DerivedStore.getState()
             , cx = React.addons.classSet
             , classes = cx({
                "spinner-is-visible": state.status === "pending"
             });

         return <div {classes}>
                   <Spinner /> # only visible if "spinner-is-visible
                   <Results results={state.derivedResults}/> # only visible if not...
                </div>;
     }

 });


 # MyService
 # =========================================================

 # ensure derived data is invalidated by updates in it's source?
 OriginalStore.addListener(function () {
     setTimeout(function () {
        dispatcher.dispatch({
            type: "refreshDerivedData"
        })
     }, 0); 

 });

我喜欢这种方法的地方在于,视图将 DerivedStore 视为它的视图模型,并且此类视图主要对其视图模型的新鲜度感兴趣。然而,我担心的是商店可能会不同步。

然后我的问题:

  • promise 方法是否可以接受?
  • 第二种方法更好/更差?如果是,为什么?
  • 是否存在解决此问题的“规范”方法?

PS:抱歉,如果这段代码中有任何基本的 linting 错误,我在过去 3 个月里一直在 Coffeescript 工作,它破坏了我的 linting 能力...

【问题讨论】:

    标签: javascript reactjs reactjs-flux


    【解决方案1】:

    所有异步操作都应该由操作的创建引起。异步操作的完成应该由另一个操作的创建发出信号。商店可能会监听这些操作,并发出更改事件。

    在您的组件中,您侦听 DerivedStore 以进行更改。可以从任何地方创建操作,例如在您的组件或其他商店中。数据(最终)派生,更新存储,发出更改事件,并且您的组件将事件有效负载应用于状态。

    总而言之,您的组件实际上并不知道幕后发生的事情是同步还是异步。这很棒,因为它允许您在幕后进行这些性能更改,而不会有损坏组件的风险。

    Pure store 通常只有一个公共函数来获取 store 的状态。在您的组件中,您应该只在 getInitialState 中调用它,或者更好的是:有一个 mixin 来执行此操作并为您添加更改侦听器。

    【讨论】:

    • 这听起来像是我正在寻找的答案......但我对商店可以创建操作的概念很感兴趣。由于存储在另一个操作期间接收更新,它必须保证后续操作发生在下一个刻度上,或者导致异常(如果使用 Facebooks Dispatcher)。你对这个问题有什么看法?
    • 我的意思是,与 webworker/server/etc 的实际交互是应该发生在动作创建者中,还是在接收动作的商店中,这是可以解释的。因此,在存储中处理初始操作时,您可以启动异步操作并在其完成处理程序/回调中创建操作。这主要取决于您使用的通量实现。
    • 好的 - 我想这已经为我们解决了 - 我只需要确保其他团队成员认识到你不能将一个动作作为另一个动作的直接结果来发送(而不是异步回调) - 我们不能让异步内容通过 Promise 从存储中泄漏到视图中
    【解决方案2】:

    听起来以下 github 上的讨论可以帮助你。

    store.getItem() 可能需要异步服务器调用: https://github.com/facebook/flux/issues/60

    管理客户端数据量: https://github.com/facebook/flux/issues/62

    获取商店数据本质上是同步的,然后组件可以告诉商店执行长时间运行的任务,但随后忘记了它。

    一旦在 store 中完成任务,就会创建一个动作并发生流程,此时组件可以从 store 同步获取所需的信息。

    这有意义吗?

    【讨论】:

    • 感谢您的链接,它确实看起来像一个类似的问题。我想我们正在苦苦挣扎,因为我们不确定 Store 是否可以在不破坏 Flux 模式的情况下监听其他 Store 或发送新的 Action。
    【解决方案3】:

    如果我要以最可能的 Flux 方式创建异步进程,我会像 XHR 请求一样处理它——在 Action Creator 或 Store 中启动异步进程(以最适合的方式)应用程序),然后在异步过程完成时调用新的 Action Creator 来调度新的操作。这样,多个 store 可以响应完成的昂贵的异步过程,并且数据流仍然来自一个 Action。

    【讨论】:

    • 我想这里需要注意的是,如果 Store 发出 Action 那么它必须是异步的以避免 Dispatcher 异常,对吧?我问的原因是我希望我们团队中不熟悉该模式的人不要在导致同步操作的操作上犯同样的错误。
    • 是的,您不希望商店在与前一个操作相同的调度/更新周期中调用操作创建者。
    【解决方案4】:

    您也可以向您的商店添加一个处理程序,当您的商店中发出某个事件时调用该处理程序

    所以让我们说在你的商店里你有一个方法:

    Store = { ...
    
       addUnreadDoneListener:function(callback){
          this.on(SOME_EVENT_CONSTANT, callback);
       },
    
    ...}
    

    在您的 componentWillMount 中,您可以使用组件的函数注册到此“addUnreadDoneListener”,然后每次您的商店发出此特定事件时都会调用该函数。

    我个人也在我的项目中这样做。而且我认为这种方式很容易管理

    希望这会有所帮助。

    编辑:我忘了提及...我使用 Eventemitter 来执行此操作。

    【讨论】:

    • 这似乎只是一个专门针对单个事件的附加侦听器接口 - 不确定它如何解决我的特定问题?
    • 对不起,我今天早上很着急-有时间我会回来重读你的帖子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-01
    相关资源
    最近更新 更多