2015-08-27 19 views
12

我正在使用Facebook的Flux Dispatcher創建一個簡單的CRUD應用程序來處理英語學習網站的帖子創建和編輯。我目前正在打交道的,看起來像這樣的API:如何在flux中處理嵌套的api調用

/posts/:post_id 
/posts/:post_id/sentences 
/sentences/:sentence_id/words 
/sentences/:sentence_id/grammars 

在節目中和編輯網頁的應用程序,我想能夠顯示所有信息特定職位以及所有的它的句子和句子的單詞和語法細節都在一個頁面上。

我遇到的問題是如何啓動所有收集所有這些數據所需的異步調用,然後將需要從所有商店獲得的數據組合到一個單獨的對象中,我可以將其設置爲狀態我的頂級組件。什麼,我一直試圖做一個電流(可怕的)例子是這樣的:

頂級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 - 其他實體(句子,語法,字)也有類似的工作方式類似ActionCreators:

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

的主要問題是,流量調度將拋出時SentencesActions.fetch是所謂的_handlePostsStoreChange回調不變錯誤「無法在調度中間派遣」 becau如果SentencesActions方法在前一個操作的調度回調完成之前觸發一個調度。

我知道我可以通過使用像_.defersetTimeout這樣的東西來解決這個問題 - 不過那真的覺得我只是在這裏修補問題。此外,我考慮在動作本身中執行所有這些獲取邏輯,但這似乎也不正確,並且會使錯誤處理更加困難。我將每個實體都分離到了自己的商店和行爲中 - 組件級別的組織不應該有某種方式來構建我需要從每個實體的各自商店購買的東西嗎?

對於任何已經完成類似事情的人的任何建議都是開放的!

+0

你嘗試過使用'waitFor'嗎? https://facebook.github.io/flux/docs/dispatcher.html – knowbody

+0

@knowbody是的,我試圖使用'waitFor',但它似乎並沒有真正解決這個問題,因爲問題是第二個動作會在第一個動作完成之前發送。但是,也許我對'waitFor'的理解是錯誤的,我只是沒有正確使用它? – joeellis

+1

@joeellis:是否有可能將jsFiddle演示放在一起,展示您的問題情況? –

回答

5

但是,沒有,在調度過程中沒有任何破解行爲,這是設計。行動不應該是導致變化的事情。他們應該像一張報紙,通知外部世界變化的應用,然後應用程序迴應這個消息。商店本身會引起變化。行動只是通知他們。

而且

組件不應該決定何時獲取數據。這是視圖層中的應用程序邏輯。

比爾費舍爾,流量https://stackoverflow.com/a/26581808/4258088

的創造者您的組件會決定何時獲取數據。這是不好的做法。 你基本上應該做的是讓你的組件通過動作指明它需要的數據。

商店應該負責累積/提取所有需要的數據。不過要注意,這家店通過API調用請求的數據之後,響應應該觸發操作,而不是店裏處理/直接節省的響應是很重要的。

你的店可能看起來像這樣的事情:

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

所有你需要做的則聽商店。還取決於你是否使用框架以及需要發送更改事件(手動)的框架。

+1

感謝您的(以及所有其他答案)。這聽起來像我錯誤的地方是我假設頂級智能組件應該作爲一個視圖控制器類型,即它應該能夠採取行動反對所有的行動和存儲它需要編寫/彙編所有的狀態它是兒童組件需要使用。看起來這個想法是錯誤的,商店應該真的在處理這些異步請求。這是一種恥辱,因爲我寧願讓這些行爲對他們負責,但唉,我認爲我已經證明,目前的流量模式是不可能的。 – joeellis

2

我想你應該有不同的存儲反映您的數據模型和一些POJO的對象反映的對象的實例。因此,您的Post對象將有一個getSentence()方法,依次調用SentenceStore.get(id)等等。您只需將一個方法(如isReady())添加到您的Post對象返回true或錯誤所有數據已被提取或不提取。

下面是一個基本的實現使用ImmutableJS

PostSore.js

var _posts = Immutable.OrderedMap(); //key = post ID, value = Post 

class Post extends Immutable.Record({ 
    'id': undefined, 
    'sentences': Immutable.List(), 
}) { 

    getSentences() { 
     return SentenceStore.getByPost(this.id) 
    } 

    isReady() { 
     return this.getSentences().size > 0; 
    } 
} 

var PostStore = assign({}, EventEmitter.prototype, { 

    get: function(id) { 
     if (!_posts.has(id)) { //we de not have the post in cache 
      PostAPI.get(id); //fetch asynchronously the post 
      return new Post() //return an empty Post for now 
     } 
     return _post.get(id); 
    } 
}) 

SentenceStore.js

var _sentences = Immutable.OrderedMap(); //key = postID, value = sentence list 

class Sentence extends Immutable.Record({ 
    'id': undefined, 
    'post_id': undefined, 
    'words': Immutable.List(), 
}) { 

    getWords() { 
     return WordsStore.getBySentence(this.id) 
    } 

    isReady() { 
     return this.getWords().size > 0; 
    } 
} 

var SentenceStore = assign({}, EventEmitter.prototype, { 

    getByPost: function(postId) { 
     if (!_sentences.has(postId)) { //we de not have the sentences for this post yet 
      SentenceAPI.getByPost(postId); //fetch asynchronously the sentences for this post 
      return Immutable.List() //return an empty list for now 
     } 
     return _sentences.get(postId); 
    } 
}) 

var _setSentence = function(sentenceData) { 
    _sentences = _sentences.set(sentenceData.post_id, new Bar(sentenceData)); 
}; 

var _setSentences = function(sentenceList) { 
    sentenceList.forEach(function (sentenceData) { 
     _setSentence(sentenceData); 
    }); 
}; 

SentenceStore.dispatchToken = AppDispatcher.register(function(action) { 
    switch (action.type) 
    { 
     case ActionTypes.SENTENCES_LIST_RECEIVED: 
      _setSentences(action.sentences); 
      SentenceStore.emitChange(); 
      break; 
    } 
}); 

WordStore.js

var _words = Immutable.OrderedMap(); //key = sentence id, value = list of words 

class Word extends Immutable.Record({ 
    'id': undefined, 
    'sentence_id': undefined, 
    'text': undefined, 
}) { 

    isReady() { 
     return this.id != undefined 
    } 
} 

var WordStore = assign({}, EventEmitter.prototype, { 

    getBySentence: function(sentenceId) { 
     if (!_words.has(sentenceId)) { //we de not have the words for this sentence yet 
      WordAPI.getBySentence(sentenceId); //fetch asynchronously the words for this sentence 
      return Immutable.List() //return an empty list for now 
     } 
     return _words.get(sentenceId); 
    } 

}); 

var _setWord = function(wordData) { 
    _words = _words.set(wordData.sentence_id, new Word(wordData)); 
}; 

var _setWords = function(wordList) { 
    wordList.forEach(function (wordData) { 
     _setWord(wordData); 
    }); 
}; 

WordStore.dispatchToken = AppDispatcher.register(function(action) { 
    switch (action.type) 
    { 
     case ActionTypes.WORDS_LIST_RECEIVED: 
      _setWords(action.words); 
      WordStore.emitChange(); 
      break; 
    } 

}); 

通過這樣做,你只需要聽上面的商店在組件發生變化,寫這樣的(僞代碼)

YourComponents.jsx

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

當用戶點擊頁面,PostStore將首先通過Ajax檢索Post對象和所需數據將由SentenceStoreWordStore加載。由於我們正在聆聽他們,並且方法Post僅在郵件句子準備好時返回true,並且isReady()方法Sentence僅返回true當它的所有單詞都已加載時,您無事可做:)只需等待微調器當您的數據準備就緒後,請將其替換爲您的帖子!

0

我不知道你的應用程序的狀態是如何處理的,但對我來說總是最好的作品時,我遇到的問題與Flux是更多的國家和更多的邏輯移動商店系統。我多次試圖繞過這個道路,結果總是讓我咬牙切齒。因此,在最簡單的例子中,我會派遣一個處理整個請求的動作,以及任何與之相關的狀態。這裏是一個非常簡單的例子,應該是相對通量框架不可知:

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'; 
     } 
     } 
    } 
    } 
} 
在你反應的組分

然後使用store.loading_state,以確定是否呈現某種負載飛旋,錯誤或數據正常。

請注意,在這種情況下,動作不執行任何超過傳遞一個選項對象下降到一個存儲方法,然後處理所有與在一個地方多個請求相關聯的邏輯和狀態的。

讓我知道如果我能更好地解釋這一點。