Flux应用程序应该在哪里提交ajax请求?
我正在用flux架构创build一个react.js应用程序,我想知道在什么地方和什么时候从服务器请求数据。 有没有这样的例子。 (不是TODO应用程序!)
我非常支持将asynchronous写入操作放在操作创build者和asynchronous读取操作中。 目标是在完全同步的动作处理程序中保持存储状态修改代码; 这使得他们简单的推理和简单的unit testing。 为了防止对同一端点的多个同时请求(例如,双重读取),我将把实际的请求处理移动到一个单独的模块中,该模块使用promise来防止多个请求; 例如:
class MyResourceDAO { get(id) { if (!this.promises[id]) { this.promises[id] = new Promise((resolve, reject) => { // ajax handling here... }); } return this.promises[id]; } }
虽然读取存储涉及asynchronous函数,但有一个重要的警告,即app store不会在asynchronous处理程序中更新自己,而是在响应到达时触发一个操作并仅触发一个操作。 此操作的处理程序最终将执行实际的状态修改。
例如,一个组件可能会这样做:
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 } }
Fluxxor有一个与APIasynchronous通信的例子 。
这篇博文有关于它的讨论,并已在React的博客上发布。
我发现这是一个非常重要和困难的问题,因为与后端的前端软件同步仍然是一个痛苦,所以还没有清楚地回答。
应该在JSX组件中进行API请求吗? 店? 其他地方?
在商店中执行请求意味着,如果2个商店需要相同的数据来执行给定的操作,他们将发出2个类似的请求(除非您介绍商店之间的依赖关系, 我真的不喜欢 )
就我而言,我发现把Q承诺作为行动的有效载荷非常方便,因为:
- 我的行为不需要可序列化(我不保留事件日志,我不需要事件采购的事件重播function)
- 它不需要有不同的动作/事件(请求触发/请求完成/请求失败),并且必须在并发请求被触发时使用关联ID匹配它们。
- 它允许多个存储来监听同一请求的完成,而不会在存储之间引入任何依赖关系(但是最好引入一个caching层)?
Ajax是EVIL
我认为Ajax在不久的将来会越来越less,因为很难推理。 正确的方式? 考虑到设备是分布式系统的一部分,我不知道我第一次遇到这个想法(也许在这个鼓舞人心的Chris Grangervideo中 )。
想想看。 现在为了可伸缩性,我们使用最终一致性的分布式系统作为存储引擎(因为我们不能击败CAP定理,并且经常希望可用)。 这些系统不通过轮询彼此(除了可能的共识操作?),而是使用结构,如CRDT和事件日志,使分布式系统的所有成员最终一致(成员将收敛到相同的数据,给予足够的时间) 。
现在想想什么是移动设备或浏览器。 它只是分布式系统的成员,可能会受到networking延迟和networking分区的影响。 (即你在地铁上使用你的智能手机)
如果我们可以构buildnetworking分区和networking速度容忍数据库(我的意思是我们仍然可以对孤立节点执行写入操作),那么我们可以构build受这些概念启发的前端软件(移动或桌面),支持离线模式框没有应用程序function不可用。
我认为我们应该激发我们自己的关于数据库如何工作来构build我们的前端应用程序。 有一点需要注意的是,这些应用程序不会执行POST和PUT和GET ajax请求来向对方发送数据,而是使用事件日志和CRDT来确保最终的一致性。
那么为什么不在前端做呢? 请注意,后端已经朝着这个方向发展,像卡夫卡这样的工具被大玩家大量采用。 这也与事件采购/ CQRS / DDD有关。
查看来自Kafka作者的这些精彩文章,说服自己:
- stream处理,事件采购,活跃,CEP …并使其感知全部
- 日志:每个软件工程师应该了解实时数据的统一抽象 。
也许我们可以先发送命令到服务器,然后接收一系列服务器事件(通过websockets例子),而不是发起Ajax请求。
我从来没有对Ajax请求很舒服。 由于我们React开发人员往往是function程序员。 我认为很难推断本地数据应该是你的前端应用程序的“真相源”,而事实的真正来源实际上是在服务器数据库上,而你的“本地”真相来源可能已经过时了当你收到它,并永远不会收敛到真正的价值来源,除非你按下一些蹩脚的刷新button…这是工程?
然而,由于一些显而易见的原因,devise这样的东西还是有点困难:
- 您的移动/浏览器客户端资源有限,无法在本地存储所有数据(因此有时需要使用ajax请求大量内容进行轮询)
- 您的客户端不应该看到分布式系统的所有数据,因此它需要以某种方式过滤出于安全原因而收到的事件
您可以在动作创build者或商店中调用数据。 重要的是不直接处理响应,而是在错误/成功callback中创build一个操作。 直接在商店中处理响应会导致更脆弱的devise。
我一直在使用Binary Muse的Fluxxor ajax例子 。 这是我使用相同方法的非常简单的例子。
我有一个简单的产品存储一些产品操作和控制器视图组件,它具有所有响应产品商店所做更改的子组件。 例如产品滑块 , 产品列表和产品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%同步。
我在这里回答了一个相关的问题: 如何在stream量中处理嵌套的api调用
行动不应该是导致变化的事情。 他们应该像一个报纸,通知应用外部世界的变化,然后应用程序响应这个消息。 商店本身就会引起变化。 行动只是通知他们。
Bill Fisher,Flux的创build者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 } }
以下是我对此的看法: http : //www.thedreaming.org/2015/03/14/react-ajax/
希望有所帮助。 🙂