Как обрабатывать вложенные вызовы api в flux
Я создаю простое приложение CRUD с помощью Диспетчера потока Facebook для обработки создания и редактирования сообщений для сайта изучения английского языка. В настоящее время я имею дело с 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
обратный вызов, потому что этот метод SentencesActions запускает отправку до завершения обратного вызова отправки для предыдущего действия.
Я знаю, что могу исправить это, используя что-то вроде _.defer
или setTimeout
- однако это действительно похоже на то, что я просто исправляю проблему здесь. Кроме того, я подумал выполнение всей этой логики извлечения в самих действиях, но это тоже казалось неправильным и сделало бы обработку ошибок более сложной. У меня каждая из моих сущностей разделена на свои собственные хранилища и действия - разве не должен быть какой-то способ на уровне компонентов составить то, что мне нужно из соответствующих хранилищ каждой сущности?
Откройте для любых советов от тех, кто совершил что-то подобное!
3 ответов
но нет, нет никакого взлома, чтобы создать действие в середине отправки, и это по дизайну. Действия не должны быть вещами, которые вызывают изменения. Они должны быть похожи на газету, которая информирует приложение об изменении внешнего мира, а затем приложение реагирует на эту новость. Магазины сами по себе вызывают изменения. Действия просто информируют их.
и
компоненты не должны решать, когда получить данные. Это логика приложения в слое view.
Билл Фишер, создатель 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);
}
}
все, что вам нужно сделать, это слушать в магазинах. Также в зависимости от того, используете ли вы фреймворк и какой из них вам нужно испустить событие изменения (вручную).
Я думаю, что у вас должен быть другой магазин, отражающий ваши модели данных, и некоторые объекты POJO, отражающие экземпляры вашего объекта. Таким образом, ваш
Я не знаю, как обрабатывается ваше состояние приложения, но для меня система, которая всегда работает лучше всего, когда я сталкиваюсь с проблемами с 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
чтобы определить, следует ли отображать какой-либо загрузочный счетчик, ошибку или данные как обычно.
обратите внимание, что в этом случае действие не делает ничего, кроме передачи объекта options в метод store, который затем обрабатывает всю логику и состояние, связанные с несколькими запросами в одном месте.
Дайте мне знать, если я могу объяснить это лучше.