[英]How do I handle API calls with actions in the React.js Flux architecture and McFly?
[英]How to handle nested api calls in flux
我正在使用Facebook的Flux Dispatcher創建一個簡單的CRUD應用程序來處理英語學習網站的帖子的創建和編輯。 我目前正在處理一個看起來像這樣的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 - 其他實體(句子,語法,單詞)也有類似的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)
});
}
}
主要問題是當在_handlePostsStoreChange
回調中調用SentencesActions.fetch
時,Flux調度程序拋出“無法在調度中間發送”不變錯誤,因為SentencesActions方法在前一個操作的調度回調完成之前觸發調度。
我知道我可以通過使用像_.defer
或setTimeout
類的東西來解決這個問題 - 但是這真的感覺我只是在這里修補問題。 此外,我考慮在動作本身中執行所有這些獲取邏輯,但這似乎也不正確,並且會使錯誤處理更加困難。 我將每個實體分離到自己的存儲和操作中 - 組件級別是否應該有某種方式來構建每個實體各自存儲所需的內容?
向任何已完成類似工作的人提供任何建議!
但不,沒有黑客可以在調度過程中創建一個動作,這是設計的。 行動不應該是引起變化的事情。 它們應該像報紙一樣通知應用程序外部世界的變化,然后應用程序響應該新聞。 商店本身會引起變化。 行動告訴他們。
也
組件不應該決定何時獲取數據。 這是視圖層中的應用程序邏輯。
Bill Fisher,Flux的創建者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);
}
}
您需要做的就是收聽商店。 還取決於您是否使用框架以及您需要發出哪個更改事件(手動)。
我認為您應該有不同的Store反映您的數據模型和一些反映您的對象實例的POJO對象。 因此,你的Post
對象將有一個getSentence()
方法,它們將調用SentenceStore.get(id)
等。你只需要將一個方法,如isReady()
到你的Post
對象返回true
或`false wether all數據是否已獲取。
這是使用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對象,並且SentenceStore
和WordStore
將加載所需的數據。 由於我們正在監聽它們並且Post
的isReady()
方法僅在post的句子准備就緒時返回true
,並且Sentence
isReady()
方法僅在所有單詞都已加載時返回true
,否則無關:)只需等待當您的數據准備就緒時,您的帖子將替換微調器!
我不知道你的應用程序狀態是如何處理的,但對我來說,當遇到Flux問題時,總是運行得最好的系統是將更多狀態和更多邏輯移動到商店。 我試圖多次繞過這個問題,它總是咬我。 因此,在最簡單的示例中,我將調度一個處理整個請求的操作,以及隨之而來的任何狀態。 這是一個非常簡單的例子,應該是相對與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';
}
}
}
}
}
然后在您的React組件中使用store.loading_state
來確定是否正常呈現某種加載微調器,錯誤或數據。
請注意,在這種情況下,操作只會將選項對象傳遞給存儲方法,然后存儲方法會在一個位置處理與多個請求關聯的所有邏輯和狀態。
如果我能更好地解釋這一點,請告訴我。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.