Как иметь дело с отношениями в Flux?

Представьте себе что-то вроде Quora.

[
  {
    type: "question",
    answers: [
      {
        type: "answer",
        upvotes: [
          {
            type: "upvote"
          }
          /* more upvotes */
        ],
        comments [
          {
            type: "comment"
          }
          /* more comments */
        ]
      }
      /* more answers */
    ]
  }
  /* more questions */
]

У меня наверняка есть что-то вроде QuestionsStore. Но для всех дочерних объектов я не уверен, что с ними делать. Исходя из Backbone, я думаю, что каждый ответ должен иметь UpvotesStore и CommentsStore, а компоненты получат свои данные из этих Магазинов и подписываются на обновления от них. Насколько я понимаю Flux, "дочерние" /реляционные хранилища несколько необычны.

Когда каждый компонент подписывается на обновления от QuestionsStore, что приводит к чему-то вроде:

/* in CommentsComponent */
onUpdate: function() {
  this.setState({
    comments: QuestionsStore.getComments({questionId: 1, answerId: 1});
  });
}

или более экстремальный:

/* in CommentComponent */
onUpdate: function() {
  this.setState(QuestionsStore.getComment({questionId: 1, answerId: 1, commentId: 1}));
}

Поскольку реляционные данные живут в древовидной структуре, каждый компонент должен знать весь "родительский" идентификатор, чтобы иметь возможность запрашивать свои данные из QuestionsStore. Я нахожу это как-то странным.

Итак, каков наилучший шаблон потока для работы с реляционной структурой (один ко многим)?

Ответ 1

Два основных варианта, которые я вижу, - это сохранить вложенную структуру или проанализировать ее в плоской структуре, где каждый объект содержит ссылки на другие объекты в соответствии с их отношениями. Я думаю, что лучший подход будет зависеть от того, как данные должны быть доступны.

Вложенная структура

Сохранение вложенной структуры делает вещи относительно легкими, если она всегда отражает вашу иерархию представлений. Например, рассмотрим:

// render a <Question/>
render: function() {
  var question = this.props.data,
      answers = question.answers.map(function(answer, i) {
        return <Answer key={i} data={answer}/>
      });

  return (
    <div className="question">
      {answers}
    </div>
  );
}

// render an <Answer/>
render: function() {
  var answer = this.props.data,
      comments = answer.comments.map(function(comment, i) {
        return <Comment key={i} data={comment}/>
      });

  return (
    <div className="answer">
      ...
      {comments}
    </div>
  );
}

// and so on

Наличие компонента верхнего уровня для получения данных из хранилища и передачи его через реквизиты, как это намного проще, чем управление каждым компонентом, отслеживание индексов для поиска вложенных данных в Магазине.

Потенциальным недостатком этого подхода является то, что вложенные данные сложнее получить из-за пределов контекста. Например, если вы хотите отслеживать все комментарии данного Пользователя, вашим пользовательским данным все равно придется прибегать к индексам, чтобы отслеживать, какие вопросы, на которые отвечают индексы на один вопрос, и какие индексы комментариев для каждого ответа указывают на правильные комментарии. В таких случаях вы можете воспользоваться плоской структурой данных.

Плоская структура

Плоская структура требует больше работы для настройки, но имеет то преимущество, что каждый объект сразу доступен. Предполагая, что ваши данные поступают в виде вложенных объектов, его нужно проанализировать, пройдя через дерево, чтобы найти все узлы, дать каждому уникальный ключ, заменить его поддеревья ссылками на соответствующие ключи, а затем сохранить все результаты в соответствующие магазины. Но, как отметил Дэн Абрамов, кто-то уже предоставил модуль, чтобы облегчить это.

Теперь ваши вопросы могут относиться к ответам, и каждый ответ может ссылаться на комментарии, что делает тривиальным воссоздать исходную вложенную структуру; но ваш Пользователь также может ссылаться на комментарии, не задумываясь о том, как они связаны с ответами или вопросами.

В основном это как Git работает внутри; он особенно эффективен для деревьев данных, которые содержат много избыточности (например, несколько коммитов, которые включают неизменный файл, могут указывать на один и тот же объект).