在 Flux 应用程序中应该在哪里提出 ajax 请求?

IT技术 javascript reactjs reactjs-flux
2021-01-22 11:05:13

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

6个回答

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

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

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

例如,一个组件可能会:

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
  }
}
@SebastienLorber 对我来说,flux 的最大吸引力是将所有状态更新保持在同步代码路径中,并且明确地作为动作调度的结果,因此我避免了商店内部的异步。
2021-03-18 11:05:13
@Federico 我仍然不清楚“最佳”解决方案是什么。我一直在试验这种数据加载策略,并结合计算未完成的异步请求的数量。不幸的flux是在构造后被注入到商店中,所以在 initialize 方法中没有很好的方法来获取操作。您可能会从 Yahoo 的 isomorophic flux libs 中找到一些好主意;这是 Fluxxor v2 应该更好地支持的东西。如果您想更多地讨论这个问题,请随时给我发电子邮件。
2021-03-21 11:05:13
我发现这个旧线程非常有帮助 - 特别是 Bill Fisher 和 Jing Chen 的评论。这与@BinaryMuse 所提议的非常接近,只是在动作创建者中发生分派的细微差别。
2021-03-21 11:05:13
data: result应该是data : data吧?没有result也许更好地将数据参数重命名为有效载荷或类似的东西。
2021-04-03 11:05:13
您是否尝试将Promise放入操作有效负载中?我发现它比分派多个动作更容易处理
2021-04-09 11:05:13

Fluxxor 有一个与 API 进行异步通信的示例

这篇博文对此进行了讨论,并在 React 的博客上进行了专题介绍。


我发现这是一个非常重要和困难的问题,目前还没有明确回答,因为前端软件与后端同步仍然很痛苦。

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

在商店中执行请求意味着如果 2 个商店需要相同的数据来执行给定的操作,它们将发出 2 个类似的请求(除非您在商店之间引入依赖关系,我真的不喜欢

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

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

Ajax是邪恶的

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

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

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

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

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

那么为什么不在前端这样做呢?请注意,后端已经在朝着这个方向发展,像 Kafka 这样的工具被大玩家大量采用。这也与事件溯源/CQRS/DDD 有某种关系。

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

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

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

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

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

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

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

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

我有一个简单的产品存储一些产品操作控制器视图组件,其中包含所有响应对产品存储所做的更改的子组件例如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% 同步。

我在这里回答了一个相关的问题:如何处理不断变化的嵌套 api 调用

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

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

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

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

商店可能看起来像这样:

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