如何在 Flux 应用程序中发送 AJAX 请求?

我正在用 flux 架构创建一个 react.js 应用程序,但我不知道应该在何时何地从服务器发出请求来获取对应的数据。有什么例子吗? (不是代办清单相关应用!)

40601 次浏览

Fluxxor具有与API的异步通信的一个例子

这个博客有关于它的讨论,并在React的博客上有特别介绍。


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

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

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

在我的例子中,我发现将Q承诺作为行动的有效载荷非常方便,因为:

  • 我的动作不需要序列化(我不保留事件日志,我不需要事件源的事件回放功能)
  • 它不需要有不同的动作/事件(请求触发/请求完成/请求失败),也不需要在并发请求可以触发时使用关联id来匹配它们。
  • 它允许多个存储侦听同一个请求的完成,而不需要在存储之间引入任何依赖关系(然而引入缓存层可能会更好?)

Ajax是邪恶的

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

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

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

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

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

所以为什么不在前端做这个呢? 请注意,后端已经在朝着这个方向发展,像Kafka这样的工具被大公司大量采用。这在某种程度上也与事件来源/ CQRS / DDD有关

看看卡夫卡作者写的这些很棒的文章,说服你自己:

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

我对Ajax请求一直不太熟悉。我们React的开发人员倾向于函数式程序员。我认为很难对本地数据进行推理,这些数据应该是前端应用程序的“真相来源”,而真正的真相来源实际上在服务器数据库中,并且当您接收到它时,您的“本地”真相来源可能已经过时,并且永远不会收敛到真正的真相价值来源,除非您按下一些蹩脚的刷新按钮……这是工程吗?

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

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

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

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

可以在操作创建者或存储区中调用数据。重要的是不要直接处理响应,而是在error/success回调中创建一个操作。直接在商店中处理响应会导致更脆弱的设计。

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

希望这能有所帮助。:)

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

我有一个简单的产品储存,一些产品的行为控制器视图组件,它有一个子组件,它们都响应对产品储存所做的更改。例如product-slider产品列表产品搜索组件。

假冒产品客户端

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

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的创造者

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

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

A商店看起来像这样:

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