Javascript 如何在flux中处理嵌套api调用
我正在使用Facebook的Flux Dispatcher创建一个简单的CRUD应用程序,用于为英语学习网站创建和编辑帖子。我目前正在处理的api如下所示:Javascript 如何在flux中处理嵌套api调用,javascript,reactjs,flux,Javascript,Reactjs,Flux,我正在使用Facebook的Flux Dispatcher创建一个简单的CRUD应用程序,用于为英语学习网站创建和编辑帖子。我目前正在处理的api如下所示: /posts/:post_id /posts/:post_id/sentences /sentences/:sentence_id/words /sentences/:sentence_id/grammars class Posts { constructor() { this.posts = []; this.bi
/posts/:post_id
/posts/:post_id/sentences
/sentences/:sentence_id/words
/sentences/:sentence_id/grammars
class Posts {
constructor() {
this.posts = [];
this.bindListeners({
handlePostNeeded: PostsAction.POST_NEEDED,
handleNewPost: PostsAction.NEW_POST
});
}
handlePostNeeded(id) {
if(postNotThereYet){
api.posts.fetch(id, (err, res) => {
//Code
if(success){
PostsAction.newPost(payLoad);
}
}
}
}
handleNewPost(post) {
//code that saves post
SentencesActions.needSentencesFor(post.id);
}
}
在应用程序的“显示和编辑”页面上,我希望能够在一个页面上显示给定帖子的所有信息、所有句子、句子的单词和语法细节
我遇到的问题是如何启动收集所有这些数据所需的所有异步调用,然后将所有存储区中需要的数据组合到一个对象中,我可以将该对象设置为顶级组件中的状态。我一直试图做的一个最新(糟糕)的例子是:
顶层PostsShowView:
class PostsShow extends React.Component {
componentWillMount() {
// this id is populated by react-router when the app hits the /posts/:id route
PostsActions.get({id: this.props.params.id});
PostsStore.addChangeListener(this._handlePostsStoreChange);
SentencesStore.addChangeListener(this._handleSentencesStoreChange);
GrammarsStore.addChangeListener(this._handleGrammarsStoreChange);
WordsStore.addChangeListener(this._handleWordsStoreChange);
}
componentWillUnmount() {
PostsStore.removeChangeListener(this._handlePostsStoreChange);
SentencesStore.removeChangeListener(this._handleSentencesStoreChange);
GrammarsStore.removeChangeListener(this._handleGrammarsStoreChange);
WordsStore.removeChangeListener(this._handleWordsStoreChange);
}
_handlePostsStoreChange() {
let posts = PostsStore.getState().posts;
let post = posts[this.props.params.id];
this.setState({post: post});
SentencesActions.fetch({postId: post.id});
}
_handleSentencesStoreChange() {
let sentences = SentencesStore.getState().sentences;
this.setState(function(state, sentences) {
state.post.sentences = sentences;
});
sentences.forEach((sentence) => {
GrammarsActions.fetch({sentenceId: sentence.id})
WordsActions.fetch({sentenceId: sentence.id})
})
}
_handleGrammarsStoreChange() {
let grammars = GrammarsStore.getState().grammars;
this.setState(function(state, grammars) {
state.post.grammars = grammars;
});
}
_handleWordsStoreChange() {
let words = WordsStore.getState().words;
this.setState(function(state, words) {
state.post.words = words;
});
}
}
这是我的PostsActions.js-其他实体(句子、语法、单词)也有类似的ActionCreator,它们以类似的方式工作:
let api = require('api');
class PostsActions {
get(params = {}) {
this._dispatcher.dispatch({
actionType: AdminAppConstants.FETCHING_POST
});
api.posts.fetch(params, (err, res) => {
let payload, post;
if (err) {
payload = {
actionType: AdminAppConstants.FETCH_POST_FAILURE
}
}
else {
post = res.body;
payload = {
actionType: AdminAppConstants.FETCH_POST_SUCCESS,
post: post
}
}
this._dispatcher.dispatch(payload)
});
}
}
<> P>主要的问题是磁通调度器在“代码>语句行为”时抛出“不能在调度中间”发送的错误。在代码< > HooLeStsStuteChchange < /Cuff>回调中调用FETCH <代码>,因为StEncess Actudio方法在前一个动作的调度回调结束之前触发调度。p>
我知道我可以通过使用类似于.defer
或setTimeout
之类的方法来解决这个问题,但这确实让我感觉我只是在修补这个问题。此外,我考虑在操作本身中执行所有这些获取逻辑,但这似乎也不正确,并且会使错误处理更加困难。我已经将我的每个实体分离到它们自己的存储和操作中——难道在组件级别不应该有某种方式来从每个实体各自的存储中组合我需要的东西吗
愿意听取任何有类似成就的人的建议
但不,在调度的中间没有创建一个动作,这是设计的。行动不应该是导致改变的事情。它们应该像一份报纸,通知应用程序外部世界的变化,然后应用程序响应该消息。商店本身会引起变化。行动只是通知他们
也 组件不应该决定何时获取数据。这是视图层中的应用程序逻辑 比尔·费希尔,《通量》的创作者 组件正在决定何时提取数据。这是个坏习惯。 您基本上应该做的是让您的组件通过操作声明它需要什么数据 存储区应负责积累/获取所有需要的数据。但是需要注意的是,在存储通过API调用请求数据之后,响应应该触发一个操作,而不是存储直接处理/保存响应 您的商店可能看起来像这样:/posts/:post_id
/posts/:post_id/sentences
/sentences/:sentence_id/words
/sentences/:sentence_id/grammars
class Posts {
constructor() {
this.posts = [];
this.bindListeners({
handlePostNeeded: PostsAction.POST_NEEDED,
handleNewPost: PostsAction.NEW_POST
});
}
handlePostNeeded(id) {
if(postNotThereYet){
api.posts.fetch(id, (err, res) => {
//Code
if(success){
PostsAction.newPost(payLoad);
}
}
}
}
handleNewPost(post) {
//code that saves post
SentencesActions.needSentencesFor(post.id);
}
}
你所需要做的就是倾听商店的声音。还取决于您是否使用框架以及您需要(手动)发出更改事件的框架。我认为您应该有反映数据模型的不同存储,以及反映对象实例的一些POJO对象。因此,您的
Post
对象将有一个get句子()
方法,该方法依次调用SentenceStore.get(id)
等。您只需在Post
对象中添加一个方法,例如isReady()
,返回true
或`false,无论是否已获取所有数据
下面是一个基本的实现,使用:
PostSore.js
SentenceStore.js
WordStore.js
通过这样做,您只需要监听组件中的上述存储更改并编写类似的内容(伪代码)
YourComponents.jsx
当用户点击页面时,
PostStore
将首先通过Ajax检索Post对象,所需数据将由SentenceStore
和WordStore
加载。因为我们正在听它们,所以isReady()
方法的Post
只在Post的句子准备好时返回true
,而isReady()
方法的句子
只在所有单词都加载完毕时返回true
,您无事可做:)只需等待数据准备就绪时,微调器被您的帖子替换即可 我不知道应用程序的状态是如何处理的,但对我来说,当我遇到流量问题时,最有效的系统是将更多的状态和逻辑转移到存储中。我已经试过好几次了,结果它总是咬我。因此,在最简单的示例中,我将调度一个操作来处理整个请求以及与之相关的任何状态。这是一个非常简单的例子,应该是相对不可知的:
var store = {
loading_state: 'idle',
thing_you_want_to_fetch_1: {},
thing_you_want_to_fetch_2: {}
}
handleGetSomethingAsync(options) {
// do something with options
store.loading_state = 'loading'
request.get('/some/url', function(err, res) {
if (err) {
store.loading_state = 'error';
} else {
store.thing_you_want_to_fetch_1 = res.body;
request.get('/some/other/url', function(error, response) {
if (error) {
store.loading_state = 'error';
} else {
store.thing_you_want_to_fetch_2 = response.body;
store.loading_state = 'idle';
}
}
}
}
}
然后,在React组件中,使用store.loading\u state
确定是否呈现某种加载微调器、错误或数据为正常
请注意,在这种情况下,操作只不过是将一个options对象向下传递给一个store方法,然后该方法在一个位置处理与多个请求相关联的所有逻辑和状态
如果我能更好地解释这些问题,请告诉我。您是否尝试过使用
waitFor
@knowbody是的,我曾尝试使用waitFor
,但它似乎并没有真正解决这个问题,因为问题是第二个操作在第一个操作完成之前被调度。然而,也许我对waitFor
的理解是错误的,我只是没有正确地使用它?@joeellis:您是否可以制作一个JSFIDLE演示来演示您的问题情况?很难说没有看到所有的代码,但对PostActions.get()的第一次调用正在触发一个全局变化,触发_handlePostsStoreChange,然后调用
getInitialState:
return {post: PostStore.get(your_post_id)}
componentDidMount:
add listener to PostStore, SentenceStore and WordStore via this._onChange
componentWillUnmount:
remove listener to PostStore, SentenceStore and WordStore
render:
if this.state.post.isReady() //all data has been fetched
else
display a spinner
_onChange:
this.setState({post. PostStore.get(your_post_id)})
var store = {
loading_state: 'idle',
thing_you_want_to_fetch_1: {},
thing_you_want_to_fetch_2: {}
}
handleGetSomethingAsync(options) {
// do something with options
store.loading_state = 'loading'
request.get('/some/url', function(err, res) {
if (err) {
store.loading_state = 'error';
} else {
store.thing_you_want_to_fetch_1 = res.body;
request.get('/some/other/url', function(error, response) {
if (error) {
store.loading_state = 'error';
} else {
store.thing_you_want_to_fetch_2 = response.body;
store.loading_state = 'idle';
}
}
}
}
}