【问题标题】:Where should ajax request be made in Flux app?在 Flux 应用程序中应该在哪里发出 ajax 请求?
【发布时间】:2014-12-25 06:42:04
【问题描述】:

我正在创建一个具有通量架构的 react.js 应用程序,并且我正在尝试确定应该在何时何地从服务器请求数据。有没有这方面的例子。 (不是 TODO 应用!)

【问题讨论】:

    标签: javascript reactjs reactjs-flux


    【解决方案1】:

    我非常支持将异步写入操作放在操作创建器中,并将异步读取操作放在存储中。目标是将存储状态修改代码保留在完全同步的操作处理程序中;这使它们易于推理并且易于单元测试。为了防止对同一端点的多个同时请求(例如,双重读取),我将实际的请求处理移动到一个单独的模块中,该模块使用 Promise 来防止多个请求;例如:

    class MyResourceDAO {
      get(id) {
        if (!this.promises[id]) {
          this.promises[id] = new Promise((resolve, reject) => {
            // ajax handling here...
          });
        } 
        return this.promises[id];
      }
    }
    

    虽然存储中的读取涉及异步函数,但有一个重要的警告,即存储不会在异步处理程序中更新自己,而是触发一个动作,并且在响应时触发一个动作到达。此操作的处理程序最终会执行实际的状态修改。

    例如,一个组件可能会:

    getInitialState() {
      return { data: myStore.getSomeData(this.props.id) };
    }
    

    商店会实现一个方法,也许是这样的:

    class Store {
      getSomeData(id) {
        if (!this.cache[id]) {
          MyResurceDAO.get(id).then(this.updateFromServer);
          this.cache[id] = LOADING_TOKEN;
          // LOADING_TOKEN is a unique value of some kind
          // that the component can use to know that the
          // value is not yet available.
        }
    
        return this.cache[id];
      }
    
      updateFromServer(response) {
        fluxDispatcher.dispatch({
          type: "DATA_FROM_SERVER",
          payload: {id: response.id, data: response}
        });
      }
    
      // this handles the "DATA_FROM_SERVER" action
      handleDataFromServer(action) {
        this.cache[action.payload.id] = action.payload.data;
        this.emit("change"); // or whatever you do to re-render your app
      }
    }
    

    【讨论】:

    • 您是否尝试过将 Promise 放入操作负载中?我发现它比调度多个操作更容易处理
    • @SebastienLorber 对我来说,最大的吸引力在于将所有状态更新保持在同步代码路径中,并且明确作为动作调度的结果,因此我避免了内部异步商店。
    • @Federico 我仍然不清楚“最佳”解决方案是什么。我一直在试验这种数据加载策略,并结合计算未完成的异步请求的数量。不幸的是,flux 在构造后被注入到存储中,因此在初始化方法中没有很好的方法来获取操作。您可能会从 Yahoo 的同构通量库中找到一些好主意;这是 Fluxxor v2 应该更好地支持的东西。如果您想进一步讨论这个问题,请随时给我发电子邮件。
    • data: result 应该是data : data,对吧?没有result。也许更好地将数据参数重命名为有效负载或类似的东西。
    • 我发现this old thread 很有帮助——尤其是Bill Fisher 和Jing Chen 的cmets。这与@BinaryMuse 的提议非常接近,只是调度发生在动作创建者中。
    【解决方案2】:

    Fluxxor 有 an example 与 API 进行异步通信。

    这个blog post 已经讨论过它,并已在 React 的博客上发表。


    我发现这是一个非常重要且难以回答的问题,因为前端软件与后端的同步仍然很痛苦。

    应该在 JSX 组件中发出 API 请求吗?商店?其他地方?

    在 store 中执行请求意味着如果 2 个 store 对于给定的 action 需要相同的数据,它们将发出 2 个类似的 requet(除非您在 store 之间引入依赖关系,which I really don't like

    就我而言,我发现将 Q Promise 作为操作的有效负载非常方便,因为:

    • 我的操作不需要可序列化(我不保留事件日志,我不需要事件溯源的事件重放功能)
    • 它不再需要有不同的操作/事件(请求触发/请求完成/请求失败),并且在可以触发并发请求时必须使用相关 ID 来匹配它们。
    • 它允许多个 store 监听同一请求的完成,而不会在 store 之间引入任何依赖关系(但是引入缓存层可能会更好?)

    Ajax 是邪恶的

    我认为 Ajax 在不久的将来会越来越少使用,因为它很难推理。 正确的方式?将设备视为分布式系统的一部分 我不知道我是从哪里第一次想到这个想法的(可能在这个inspiring Chris Granger video)。

    考虑一下。现在为了可扩展性,我们使用具有最终一致性的分布式系统作为存储引擎(因为我们无法击败CAP theorem,而且我们通常希望可用)。这些系统不通过轮询彼此同步(可能除了共识操作?),而是使用像 CRDT 和事件日志这样的结构来使分布式系统的所有成员最终保持一致(如果有足够的时间,成员将收敛到相同的数据) .

    现在想想什么是移动设备或浏览器。它只是分布式系统的一个成员,可能会遭受网络延迟和网络分区的影响。(即您在地铁上使用智能手机)

    如果我们可以构建网络分区和网络速度容忍数据库(我的意思是我们仍然可以对隔离节点执行写入操作),我们可能会构建受这些概念启发的前端软件(移动或桌面),与离线工作良好开箱即用的模式支持,没有应用功能不可用。

    我认为我们应该真正启发自己了解数据库如何构建我们的前端应用程序。需要注意的一点是,这些应用程序不执行 POST 和 PUT 以及 GET ajax 请求来相互发送数据,而是使用事件日志和 CRDT 来确保最终的一致性。

    那么为什么不在前端做呢? 请注意,后端已经朝着这个方向发展,像 Kafka 这样的工具已被大玩家广泛采用。这也与 Event Sourcing / CQRS / DDD 有关。

    查看 Kafka 作者的这些精彩文章以说服自己:

    也许我们可以先向服务器发送命令,然后接收服务器事件流(例如通过 websockets),而不是触发 Ajax 请求。

    我从未对 Ajax 请求感到满意。在我们 React 开发人员往往是函数式程序员。我认为很难推断本地数据应该是你前端应用程序的“真相来源”,而真正的真相来源实际上是在服务器数据库上,而你的“本地”真相来源可能已经过时了当你收到它时,除非你按下一些蹩脚的刷新按钮,否则永远不会收敛到真实值的真正来源......这是工程吗?

    但是,由于一些明显的原因,设计这样的东西仍然有点困难:

    • 您的移动/浏览器客户端资源有限,不一定能在本地存储所有数据(因此有时需要使用 ajax 请求大量内容进行轮询)
    • 您的客户端不应看到分布式系统的所有数据,因此出于安全原因,它需要以某种方式过滤接收到的事件

    【讨论】:

    • 你能提供一个使用 Q Promise 的例子吗?
    • @MattFoxxDuncan 不确定这是一个好主意,因为它使“事件日志”不可序列化,并使存储在触发操作时异步更新,因此它有一些缺点但是如果它适合您的用例并且您了解这些缺点,它非常方便并减少了样板。使用 Fluxxor,您可能可以执行类似 this.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere}); 的操作
    • 完全不同意您的 AJAX 论点。事实上,读起来很烦人。你读过你的言论吗?想想赚钱的商店、游戏、应用程序——所有这些都需要 API 和 AJAX 服务器调用。如果你想要“无服务器”或类似性质的东西,请查看 Firebase,但 AJAX 在这里说我希望至少没有其他人同意你的逻辑
    • @TheBlackBenzKid 我并不是说 Ajax 将在今年完全消失(请确保我目前作为一家初创公司的 CTO 仍在基于 ajax 请求构建网站),但我是说它可能会消失,因为它不是一个足以处理最终一致性的协议,它需要流式传输而不是轮询,并且最终一致性允许应用程序可靠地离线工作(是的,你可以自己用 localstorage 破解一些东西,但是你'会有有限的离线容量,或者你的应用程序非常简单)。问题不在于缓存,而是使缓存失效。
    • @TheBlackBenzKid Firebase、Meteor 等背后的模型还不够好。您知道这些系统如何处理并发写入吗? last-write-win 而不是因果一致性/合并策略?当你的同事都在处理不可靠的连接时,你能负担得起你的同事在应用程序中的压倒性工作吗?另请注意,这些系统往往会结合很多本地和服务器建模。您是否知道任何众所周知的协作应用程序非常复杂,可以完美离线运行,并宣称自己是满意的 Firebase 用户?我没有
    【解决方案3】:

    您可以在动作创建者或商店中调用数据。重要的是不要直接处理响应,而是在错误/成功回调中创建一个动作。直接在商店中处理响应会导致设计更加脆弱。

    【讨论】:

    • 你能详细解释一下吗?假设我需要从服务器加载初始数据。在控制器视图中,我启动了一个动作 INIT,并且 Store 启动了反映此动作的异步初始化。现在,我会同意这样的想法,即当 Store 获取数据时,它只会发出更改,但不会启动操作。因此,在初始化后发出更改会告诉视图他们可以从存储中获取数据。为什么需要在成功加载时发出更改,而是开始另一个动作?!谢谢
    • Fisherwebdev,关于调用数据的商店,这样做,你不会打破 Flux 范式,我能想到的唯一两种调用数据的正确方法是使用:1.使用引导类使用 Actions 加载数据 2. 视图,再次使用 Actions 加载数据
    • 调用数据和接收数据不是一回事。 @Jim-Y:您应该仅在商店中的数据实际发生更改后才发出更改。 Yotam:不,在存储中调用数据不会破坏范式。数据只能通过操作接收,以便所有商店都可以通过进入应用程序的任何新数据得到通知。所以我们可以调用 store 中的数据,但是当响应返回时,我们需要创建一个新的 action 而不是直接处理它。这使应用程序保持灵活性和弹性,以适应新功能的开发。
    【解决方案4】:

    我一直在使用来自 Fluxxor ajax example 的 Binary Muse 示例。这是我使用相同方法的非常简单的示例。

    我有一个简单的 product store 一些 product actionscontroller-view 组件,该组件具有所有响应所做更改的子组件到产品商店。例如 product-sliderproduct-listproduct-search 组件。

    假冒产品客户

    这是一个假客户端,您可以用它来代替调用实际端点返回产品。

    var ProductClient = {
    
      load: function(success, failure) {
        setTimeout(function() {
          var ITEMS = require('../data/product-data.js');
          success(ITEMS);
        }, 1000);
      }    
    };
    
    module.exports = ProductClient;
    

    产品商店

    这是产品商店,显然这是一个非常小的商店。

    var Fluxxor = require("fluxxor");
    
    var store = Fluxxor.createStore({
    
      initialize: function(options) {
    
        this.productItems = [];
    
        this.bindActions(
          constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
          constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
        );
      },
    
      onLoadSuccess: function(data) {    
        for(var i = 0; i < data.products.length; i++){
          this.productItems.push(data.products[i]);
        }    
        this.emit("change");
      },
    
      onLoadFail: function(error) {
        console.log(error);    
        this.emit("change");
      },    
    
      getState: function() {
        return {
          productItems: this.productItems
        };
      }
    });
    
    module.exports = store;
    

    现在产品操作发出 AJAX 请求并成功触发 LOAD_PRODUCTS_SUCCESS 操作将产品返回到商店。

    产品操作

    var ProductClient = require("../fake-clients/product-client");
    
    var actions = {
    
      loadProducts: function() {
    
        ProductClient.load(function(products) {
          this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
        }.bind(this), function(error) {
          this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
        }.bind(this));
      }    
    
    };
    
    module.exports = actions;
    

    因此,从侦听此商店的任何组件调用this.getFlux().actions.productActions.loadProducts() 都会加载产品。

    您可以想象有不同的操作会响应用户交互,例如 addProduct(id) removeProduct(id) 等...遵循相同的模式。

    希望这个示例有点帮助,因为我发现这实施起来有点棘手,但肯定有助于保持我的商店 100% 同步。

    【讨论】:

      【解决方案5】:

      我在这里回答了一个相关问题:How to handle nested api calls in flux

      行动不应该是导致变化的事情。它们应该像报纸一样,通知应用程序外部世界的变化,然后应用程序响应该消息。商店本身会引起变化。行动只是通知他们。

      Bill Fisher,Flux 的创建者https://stackoverflow.com/a/26581808/4258088

      您基本上应该做的是,通过操作说明您需要什么数据。如果存储收到该操作的通知,它应该决定是否需要获取一些数据。

      商店应负责累积/获取所有需要的数据。不过需要注意的是,在存储请求数据并获得响应之后,它应该使用获取的数据触发一个动作,而不是直接处理/保存响应。

      商店可能如下所示:

      class DataStore {
        constructor() {
          this.data = [];
      
          this.bindListeners({
            handleDataNeeded: Action.DATA_NEEDED,
            handleNewData: Action.NEW_DATA
          });
        }
      
        handleDataNeeded(id) {
          if(neededDataNotThereYet){
            api.data.fetch(id, (err, res) => {
              //Code
              if(success){
                Action.newData(payLoad);
              }
            }
          }
        }
      
        handleNewData(data) {
          //code that saves data and emit change
        }
      }
      

      【讨论】:

        【解决方案6】:

        这是我对此的看法:http://www.thedreaming.org/2015/03/14/react-ajax/

        希望对您有所帮助。 :)

        【讨论】:

        • 根据指南投反对票。将答案放在外部网站上会使该网站的用处降低,并导致答案质量降低,从而降低网站的实用性。外部网址也可能会及时中断。否决票没有说明文章的有用性,顺便说一句,这非常好:)
        • 好帖子,但添加每种方法的优缺点的简短摘要会让您获得支持。在 SO 上,我们无需单击链接即可了解您的答案的要点。
        猜你喜欢
        • 2015-07-04
        • 1970-01-01
        • 2015-02-13
        • 2015-02-06
        • 2014-11-12
        • 2016-06-01
        • 2014-07-01
        • 1970-01-01
        相关资源
        最近更新 更多