Как обрабатывать отношения «один ко многим» в хранилищах Flux

Я только начинаю использовать поток (пока с редуксом), и мне интересно, как должны обрабатываться отношения.
Например, мы можем использовать Trello, у которого есть доски со столбцами, содержащими карточки.

Один из подходов состоит в том, чтобы иметь одно хранилище/редуктор для досок и хранить в нем все данные, но это означает несколько очень толстых хранилищ, поскольку они также должны содержать все действия для столбцов и карточек.

Другой подход, который я видел, заключается в разделении вложенных ресурсов, например, на BoardStore, ColumnStore и CardStore и использовании их идентификаторов в качестве ссылки.

Вот пример того, где я немного запутался: у вас может быть создатель действия с именем addCard, который отправляет запрос на сервер для создания карты со всеми данными. Если вы выполняете оптимистическое обновление, вы должны были создать объект карты в одном из ваших магазинов раньше, но вы не можете знать идентификатор, который он будет иметь, пока не получите ответный запрос.

Итак, вкратце:

  • Активация addCard
  • addCard выполняет запрос, тем временем вы возвращаете действие типа ADD_CARD_TEMP
  • вы получаете запрос и возвращаете действие типа ADD_CARD, где хранилище/редуктор изменяет идентификатор.

Есть ли рекомендуемый способ справиться с этим случаем? Вложенные хранилища/редукторы кажутся мне немного глупыми, но в противном случае вы получите очень сложные хранилища, так что на самом деле это выглядит как компромисс.


person Vincent P    schedule 26.07.2015    source источник


Ответы (1)


Да, правильно использовать идентификаторы в нескольких хранилищах, как в реляционной базе данных.

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

Карточки в вашем CardStore могут выглядеть так:

_cards: {
  'CARD_1': {
    id: 'CARD_1',
    columnID: 'COLUMN_3',
    title: 'Go to sleep',
    text: 'Be healthy and go to sleep on time.',
  },
  'CARD_2': {
    id: 'CARD_2',
    columnID: 'COLUMN_3',
    title: 'Eat green vegetables',
    text: 'They taste better with onions.',
  },
}

Обратите внимание, что я могу ссылаться на карту по идентификатору, а также могу получить идентификатор внутри объекта. Это позволяет мне иметь такие методы, как getCard(id), а также получать идентификатор конкретной карты в слое представления. Таким образом, у меня может быть метод deleteCard(id), который вызывается в ответ на действие, потому что я знаю идентификатор в представлении.

В хранилище карт у вас будет getCardsByColumn(columnID), который будет простой картой объектов карты, и это создаст массив карт, которые вы можете использовать для отображения содержимого столбца.


Что касается механики оптимистичных обновлений и того, как на нее влияет использование идентификаторов:

Вы можете использовать идентификатор на стороне клиента, установленный в том же закрытии, которое будет обрабатывать ответ XHR, и очистить идентификатор на стороне клиента, когда ответ будет успешным, или вместо этого выполнить откат при ошибке. Закрытие позволяет вам удерживать идентификатор на стороне клиента до тех пор, пока не вернется ответ.

Многие люди будут создавать модуль WebAPIUtils, который будет содержать все методы, связанные с закрытием, сохраняя идентификатор на стороне клиента и запрос/ответ. Создатель действия (или хранилище) может вызвать этот модуль WebAPIUtils, чтобы инициировать запрос.

Итак, у вас есть три действия:

  1. инициировать запрос
  2. справиться с успехом
  3. обрабатывать ответ

В ответ на действие, которое инициирует запрос, ваш магазин получает идентификатор на стороне клиента и создает запись.

В ответ на успех/ошибку ваш магазин снова получает идентификатор на стороне клиента и либо изменяет запись, чтобы она была подтвержденной записью с реальным идентификатором, либо вместо этого откатывает запись. Вы также хотели бы создать хороший UX вокруг этой ошибки, например, позволить пользователю попробовать еще раз.

Пример кода:

// Within MyAppActions
cardAdded: function(columnID, title, text) {
  var clientID = this.createUUID();
  MyDispatcher.dispatch({
    type: MyAppActions.types.CARD_ADDED,
    id: clientID,
    columnID: columnID,
    title: title,
    text: text,
  });
  WebAPIUtils.getRequestFunction(clientID, "http://example.com", {
    columnID: columnID,
    title: title,
    text: text,
  })(); 
},

// Within WebAPIUtils
getRequestFunction: function(clientID, uri, data) {
  var xhrOptions = {
    uri: uri,
    data: data,
    success: function(response) {
      MyAppActions.requestSucceeded(clientID, response);
    },
    error: function(error) {
      MyAppActions.requestErrored(clientID, error);
    },
  };
  return function() {
    post(xhrOptions);
  };
},

// Within CardStore
switch (action.type) {

  case MyAppActions.types.CARD_ADDED:
    this._cards[action.id] = {
      id: action.id,
      title: action.title,
      text: action.text,
      columnID: action.columnID,
    });
    this._emitChange();
    break;

  case MyAppActions.types.REQUEST_SUCCEEDED:
    var tempCard = this._cards[action.clientID];
    this._cards[action.id] = {
      id: action.id,
      columnID: tempCard.columnID,
      title: tempCard.title,
      text: tempCard.text,
    });
    delete this._cards[action.clientID];
    break;

  case MyAppActions.types.REQUEST_ERRORED:
    // ...
}

Пожалуйста, не слишком зацикливайтесь на деталях названий и особенностях этой реализации (могут быть опечатки или другие ошибки). Это просто пример кода для объяснения шаблона.

person fisherwebdev    schedule 28.07.2015
comment
Спасибо за ответ. Извините, если я не понял, но мой вопрос был больше о том, как организовать магазин, и я использовал оптимистическое обновление в качестве примера возможных проблем со ссылкой на что-то по идентификатору. - person Vincent P; 28.07.2015
comment
ой, извини. я обновил свой ответ. Надеюсь это ответит на твой вопрос. - person fisherwebdev; 29.07.2015