Как обрабатывать вложенные вызовы 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, который затем обрабатывает всю логику и состояние, связанные с несколькими запросами в одном месте.

Дайте мне знать, если я могу объяснить это лучше.