如何在" flux"中管理昂贵的派生计算?应用

时间:2014-12-04 11:42:44

标签: javascript reactjs reactjs-flux

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

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

我们遇到的问题是,派生商店中的操作太昂贵而无法同步执行,理想情况下会被委托给异步进程(Web worker,服务器之旅),我们想知道如何解决这个问题

我的同事建议从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视为视图模型,此类视图主要关注其视图模型的新鲜度。然而,我担心的是商店可能会失去同步。

我的问题:

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

PS:对不起,如果这段代码中有任何基本的linting错误,我在过去的3个月里一直在使用Coffeescript而且它已经破坏了我的linting权力......

4 个答案:

答案 0 :(得分:2)

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

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

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

纯商店通常只有一个公共功能,它可以获得商店的状态。在你的组件中,你应该只在getInitialState中调用它,或者更好的是:有一个mixin执行此操作并为您添加更改侦听器。

答案 1 :(得分:1)

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

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

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

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

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

这有意义吗?

答案 2 :(得分:1)

如果我打算以尽可能多的Flux方式创建一个异步进程,我会像XHR请求那样处理它 - 在Action Creator或Store中启动异步进程(以最适合的方式为准)应用程序)然后调用新的Action Creator以在异步过程完成时调度新操作。这样,多个商店就可以响应已完成的昂贵的异步流程,而数据流仍然来自一个Action。

答案 3 :(得分:0)

您还可以向商店添加处理程序,以便在商店中发出特定事件时调用该处理程序

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

Store = { ...

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

...}

在你的componentWillMount中,你可以注册到这个&#34; addUnreadDoneListener&#34;具有组件功能,每次商店发出此特定事件时都会调用该组件。

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

希望这会有所帮助。

编辑:我忘记了...我使用Eventemitter来做这件事。