如何在Redux中跟踪不同API调用的状态?

问题描述 投票:0回答:2

假设我有一个管理待办事项的应用程序,用户可以通过单击按钮添加新的待办事项。 当用户单击该按钮时,将显示一个微调器,直到todo保存到后端或请求失败。

当按钮是pressend时,将调度一个名为ADD_TODO_REQUEST的动作,然后由一个执行HTTP请求以保存待办事项的redux-observable epic拦截该动作,并根据HTTP请求结果调度ADD_TODO_COMPLETE动作或ADD_TODO_FAILED动作。

应用程序应在“add todo”按钮旁边显示一个微调器。 为此,我的状态包含一个名为isSaving的标志,当HTTP请求处于挂起状态时将设置为true,并在HTTP请求完成时重置为false。

我的初始状态看起来像这样:

{
     todos: [],
     isSaving: false
}

当应用程序启动时,将调度的第一个操作是FETCH_TODO_REQUEST,它将调用另一个API端点来获取所有todos。

应用程序应该再次显示一个微调器来通知用户正在下载待办事项,为此我已经为名为isFetching的状态添加了另一个标志。 这个新标志是必需的,因为如果我在添加待办事项时共享相同的标志,当用户只是添加待办事项时,我会为整个应用程序显示一个微调器。

我的初始状态现在看起来像这样:

{
     todos: [],
     isSaving: false,
     isFetching: false
}

这种方法对我来说很好,但是如果用户也可以删除待办事项,我必须跟踪这个额外的HTTP请求状态,因此我需要向状态添加另一个标志(可能称为isDeleting)。

请注意,我想在“添加按钮”旁边显示一个微调器,并在每个待删除的待办事项旁边显示一个微调器。这些旋转器都可以同时出现,这就是为什么单个标志不够而且我采用了这种方法。

在我可能有许多不同的并发API“动作”的情况下,我需要一个标志来表示每个可能的请求。 如果我还想显示错误,我现在需要为每个可用的API“动作”提供两个属性:一个用于表示请求正在进行中,另一个用于保存错误对象。

这种方法的问题在于它看起来非常非常冗长。

是否有一种惯用且更智能的方式来跟踪并发http请求的状态? 为触及同一“实体”的每个可能的http请求设置一个标志是否正确?

javascript redux redux-observable
2个回答
1
投票

我已经看到很多处理这个问题的方法,都有自己的优点和缺点。

每个资源都有自己的状态

虽然没有规则,但通常建议redux用户将其状态模式建模为数据库。

这主要意味着您将存储由某个ID索引的资源。在这种情况下,您有一个待办事项列表,因此您的状态可能如下所示:

{
  todosById: {
    '1': {
      id: '1',
      content: 'Do something'
    },
    '2': {
      id: '2',
      content: 'Do another thing'
    }
  }
}

如果您想要所有待办事项的数组,只要您选择状态,就可以动态创建它:

const selectTodos = (state) => Object.values(state.todosById);
/*
  [{
    id: '1',
    content: 'Do something'
  }, {
    id: '2',
    content: 'Do another thing'
  }]
*/

const selectTodoIds = (state) => Object.keys(state.todosById)
// ['1', '2']

你为什么要这样做?一个原因是它使得通过ID查找内容非常容易,这在用户流程中经常需要;例如显示事物列表然后使用选择其中之一。

适用于您描述的情况的另一个原因是我们现在可以单独跟踪每个资源的状态。因此,每个人都可以有自己的isFetchingisSaving等。或者,如果你想保证资源只在一个状态中你可以使用枚举 - 但要小心,它实际上是不可能获取和保存同时!

{
  todosById: {
    '1': {
      id: '1',
      content: 'Do something',
      isFetching: false,
      isSaving: false
    },
    '2': {
      id: '2',
      content: 'Do another thing',
      isFetching: false,
      isSaving: false
    }
  }
}

interface Todo {
  id: string;
  content: string;
  isFetching: boolean;
  isSaving: boolean;

  // or using an enum. here are some possibilities:
  enum Status { Fetching, Saving, New, Prestine, Dirty }
  status: Status;
}

当您的用户直接访问其中一个资源时,这也很自然。例如如果他们可以导航到一个Todo,你可以在todosById中填充它并跟踪它的状态。如果您以后还加载了待办事项列表,服务器的最新值将合并到我们之前加载的Todo中。

在处理新资源客户端的创建时,在将其保存到服务器之前,您可能不会拥有该ID。在那种特殊情况下,我有一个生成的临时ID,仅在客户端使用。例如'tmp-todo-1''tmp-todo-2'

{
  todosById: {
    'tmp-todo-1': {
      id: 'tmp-todo-1',
      content: 'A new todo that has never been saved yet',
      status: Status.New
    },
    '1': {
      id: '1',
      content: 'A todo that has been created but has unsaved changes',
      status: Status.Dirty
    },
    '2': {
      id: '2',
      content: 'Do another thing',
      status: Status.Prestine
    }
  }
}

分开的国家

更复杂的资源请求管理的另一个选择是使其完全独立并且不知道其获取/保存/等资源。

与此类似的东西:

{
  meta: {
    '1': {
      isFetching: false,
      isSaving: false
    },
    '2': {
      isFetching: false,
      isSaving: false
    }
  },
  todosById: {
    '1': {
      id: '1',
      content: 'Do something'
    },
    '2': {
      id: '2',
      content: 'Do another thing'
    }
  }
}

这样做的好处是不需要将资源本身与服务器上未包含的仅客户端状态合并,就像isFetching一样。

有很多变化,其中一些人创建了类似redux-resource的库,虽然我没有使用那个特定的库,所以我不能说太多。我使用自己制作的自定义抽象。


0
投票

为什么商店里没有单一的isBusy值?那么你的减速器根据ADD_TODO_..DELETE_TO_DO_..等来切换。这假设您只显示一个忙碌指示符。

© www.soinside.com 2019 - 2024. All rights reserved.