【发布时间】:2014-12-25 06:42:04
【问题描述】:
我正在创建一个具有通量架构的 react.js 应用程序,并且我正在尝试确定应该在何时何地从服务器请求数据。有没有这方面的例子。 (不是 TODO 应用!)
【问题讨论】:
标签: javascript reactjs reactjs-flux
我正在创建一个具有通量架构的 react.js 应用程序,并且我正在尝试确定应该在何时何地从服务器请求数据。有没有这方面的例子。 (不是 TODO 应用!)
【问题讨论】:
标签: javascript reactjs reactjs-flux
我非常支持将异步写入操作放在操作创建器中,并将异步读取操作放在存储中。目标是将存储状态修改代码保留在完全同步的操作处理程序中;这使它们易于推理并且易于单元测试。为了防止对同一端点的多个同时请求(例如,双重读取),我将实际的请求处理移动到一个单独的模块中,该模块使用 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
}
}
【讨论】:
flux 在构造后被注入到存储中,因此在初始化方法中没有很好的方法来获取操作。您可能会从 Yahoo 的同构通量库中找到一些好主意;这是 Fluxxor v2 应该更好地支持的东西。如果您想进一步讨论这个问题,请随时给我发电子邮件。
data: result 应该是data : data,对吧?没有result。也许更好地将数据参数重命名为有效负载或类似的东西。
Fluxxor 有 an example 与 API 进行异步通信。
这个blog post 已经讨论过它,并已在 React 的博客上发表。
我发现这是一个非常重要且难以回答的问题,因为前端软件与后端的同步仍然很痛苦。
应该在 JSX 组件中发出 API 请求吗?商店?其他地方?
在 store 中执行请求意味着如果 2 个 store 对于给定的 action 需要相同的数据,它们将发出 2 个类似的 requet(除非您在 store 之间引入依赖关系,which I really don't like)
就我而言,我发现将 Q Promise 作为操作的有效负载非常方便,因为:
Ajax 是邪恶的
我认为 Ajax 在不久的将来会越来越少使用,因为它很难推理。 正确的方式?将设备视为分布式系统的一部分 我不知道我是从哪里第一次想到这个想法的(可能在这个inspiring Chris Granger video)。
考虑一下。现在为了可扩展性,我们使用具有最终一致性的分布式系统作为存储引擎(因为我们无法击败CAP theorem,而且我们通常希望可用)。这些系统不通过轮询彼此同步(可能除了共识操作?),而是使用像 CRDT 和事件日志这样的结构来使分布式系统的所有成员最终保持一致(如果有足够的时间,成员将收敛到相同的数据) .
现在想想什么是移动设备或浏览器。它只是分布式系统的一个成员,可能会遭受网络延迟和网络分区的影响。(即您在地铁上使用智能手机)
如果我们可以构建网络分区和网络速度容忍数据库(我的意思是我们仍然可以对隔离节点执行写入操作),我们可能会构建受这些概念启发的前端软件(移动或桌面),与离线工作良好开箱即用的模式支持,没有应用功能不可用。
我认为我们应该真正启发自己了解数据库如何构建我们的前端应用程序。需要注意的一点是,这些应用程序不执行 POST 和 PUT 以及 GET ajax 请求来相互发送数据,而是使用事件日志和 CRDT 来确保最终的一致性。
那么为什么不在前端做呢? 请注意,后端已经朝着这个方向发展,像 Kafka 这样的工具已被大玩家广泛采用。这也与 Event Sourcing / CQRS / DDD 有关。
查看 Kafka 作者的这些精彩文章以说服自己:
也许我们可以先向服务器发送命令,然后接收服务器事件流(例如通过 websockets),而不是触发 Ajax 请求。
我从未对 Ajax 请求感到满意。在我们 React 开发人员往往是函数式程序员。我认为很难推断本地数据应该是你前端应用程序的“真相来源”,而真正的真相来源实际上是在服务器数据库上,而你的“本地”真相来源可能已经过时了当你收到它时,除非你按下一些蹩脚的刷新按钮,否则永远不会收敛到真实值的真正来源......这是工程吗?
但是,由于一些明显的原因,设计这样的东西仍然有点困难:
【讨论】:
this.dispatch("LOAD_DATA", {dataPromise: yourPromiseHere}); 的操作
您可以在动作创建者或商店中调用数据。重要的是不要直接处理响应,而是在错误/成功回调中创建一个动作。直接在商店中处理响应会导致设计更加脆弱。
【讨论】:
我一直在使用来自 Fluxxor ajax example 的 Binary Muse 示例。这是我使用相同方法的非常简单的示例。
我有一个简单的 product store 一些 product actions 和 controller-view 组件,该组件具有所有响应所做更改的子组件到产品商店。例如 product-slider、product-list 和 product-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% 同步。
【讨论】:
我在这里回答了一个相关问题: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
}
}
【讨论】:
这是我对此的看法:http://www.thedreaming.org/2015/03/14/react-ajax/
希望对您有所帮助。 :)
【讨论】: