小编典典

如何处理Flux商店中的一对多关系

reactjs

我只是开始使用flux(现在使用redux),并且想知道应该如何处理关系。
例如,我们可以使用Trello,其板卡的列包含卡。

一种方法是在板上有一个存储/还原器,并在其中存储所有数据,但这意味着要存储很多数据,因为它们还必须包含列和卡的所有操作。

我见过的另一种方法是将嵌套资源分为例如BoardStore,ColumnStore和CardStore,并使用其ID作为参考。

这是一个让我有些困惑的示例:您可能有一个名为addCard的动作创建者,该动作创建者向服务器发出请求以创建包含所有数据的卡。如果您要进行乐观更新,那么您之前可能已经在您的一家商店中创建了证件对象,但是直到您取回请求之前,您才知道其证件号。

简而言之:

  • 触发addCard
  • addCard发出请求,与此同时,您返回ADD_CARD_TEMP类型的操作
  • 您收到请求并返回ADD_CARD类型的操作,其中存储/还原程序更改了ID。

有没有建议的方法来处理这种情况?嵌套的存储/减速器对我来说看起来有点愚蠢,但否则您最终会遇到非常复杂的存储,因此看起来确实是一种折衷。


阅读 228

收藏
2020-07-22

共1个答案

小编典典

是的,正确地在多个商店中使用id就像关系数据库一样。

在您的示例中,假设您要乐观地将新卡放在特定的列中,并且一张卡只能位于一列中(多张卡中的一列)。

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.',
  },
}

请注意,我可以通过ID引用卡片,也可以在对象内检索ID。这使我可以使用类似的方法,getCard(id)并且还可以在视图层中检索特定卡的ID。因此,我可以有一个deleteCard(id)响应动作而调用的方法,因为我知道视图中的id。

在卡存储中,您将拥有getCardsByColumn(columnID),这是卡对象上的简单映射,这将产生卡阵列,可用于呈现列的内容。


关于乐观更新的机制,以及ID的使用如何影响它:

您可以使用在将处理XHR响应的同一个闭包中建立的客户端ID,并在响应成功返回时清除客户端ID,或者在发生错误时回滚。闭包允许您保留客户端ID,直到响应返回。

很多人会创建一个WebAPIUtils模块,其中将包含与关闭相关的所有方法,并保留客户端ID和请求/响应。动作创建者(或商店)可以调用此WebAPIUtils模块来发起请求。

因此,您有以下三个动作:

  1. 发起请求
  2. 处理成功
  3. 处理回应

作为对发起请求的操作的响应,您的商店会收到客户端ID并创建记录。

作为对成功/错误的响应,您的商店再次接收到客户端ID,然后将记录修改为具有真实ID的已确认记录,或者回滚该记录。您还希望围绕该错误创建一个良好的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:
    // ...
}

请不要太在意名称的细节和此实现的细节(可能存在拼写错误或其他错误)。这只是解释模式的示例代码。

2020-07-22