如何跟踪 Redux 中不同 API 调用的状态?
How can I track state of different API calls in Redux?
假设我有一个管理待办事项的应用程序,用户可以通过单击按钮添加新的待办事项。
当用户单击按钮时,将出现一个微调器,直到将待办事项保存到后端或请求失败。
当按钮被按下时,一个名为 ADD_TODO_REQUEST 的动作被调度,然后将被 redux-observable 史诗拦截,该史诗将执行 HTTP 请求保存待办事项,并将根据 HTTP 请求结果分派 ADD_TODO_COMPLETE 操作或 ADD_TODO_FAILED 操作。
应用程序应在 "add todo" 按钮旁边显示一个微调器。
为此,我的状态包含一个名为 isSaving 的标志,当 HTTP 请求挂起时该标志将设置为 true,当 HTTP 请求完成时将重置为 false。
我的初始状态形状是这样的:
{
todos: [],
isSaving: false
}
当应用程序启动时,将调度的第一个操作是 FETCH_TODO_REQUEST,它将调用另一个 API 端点以获取所有待办事项。
应用程序应该再一次显示一个微调器来通知用户正在下载待办事项,为此我在状态中添加了另一个标志,称为 isFetching。
这个新标志是必需的,因为如果我在添加待办事项时共享相同的标志,我会在用户仅添加待办事项时为整个应用程序显示一个微调器。
我的初始状态形状现在看起来像这样:
{
todos: [],
isSaving: false,
isFetching: false
}
这种方法对我来说看起来不错,但是如果用户也可以删除待办事项,我必须跟踪这个额外的 HTTP 请求状态,因此我需要添加另一个标志(可能称为 isDeleting ) 状态。
请注意,我想在 "add button" 旁边显示一个微调器,并在每个要删除的待办事项旁边显示一个微调器。这些微调器可以同时出现,这就是为什么一个标志是不够的,我已经采用了这种方法。
在我可能有许多不同并发的情况下 API "actions" 我需要为每个可能的请求设置一个标志。
如果我还想显示错误,我现在需要为每个可用的 API "action" 设置两个属性:一个表示请求正在进行,另一个表示错误 object。
这种方法的问题在于它看起来非常、非常、冗长。
是否有一种惯用且更智能的方法来跟踪并发 http 请求的状态?
为触及相同 "entity" 的每个可能的 http 请求设置一个标志是否正确?
为什么商店中没有一个 isBusy
值?然后你的减速器根据 ADD_TODO_..
、DELETE_TO_DO_..
等切换它。这假设您只显示一个忙碌指示器。
我见过很多处理这个问题的方法,各有优缺点。
每个资源都有自己的状态
虽然没有规则,但通常建议 redux 用户将他们的状态模式建模为数据库。
这主要意味着您将存储由某个 ID 索引的资源。在这种情况下,您有一个待办事项列表,因此您的状态可能如下所示:
{
todosById: {
'1': {
id: '1',
content: 'Do something'
},
'2': {
id: '2',
content: 'Do another thing'
}
}
}
当你想要一个包含所有待办事项的数组时,只要你 select 你的状态:
就可以即时创建它
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 查找内容变得非常容易,这在用户流程中经常需要;例如显示事物列表然后使用 selects 其中之一。
适用于您描述的情况的另一个原因是我们现在可以单独跟踪每个资源的状态。所以每个人都可以有自己的 isFetching
、isSaving
等。或者,如果你想保证资源只处于单一状态,你可以使用枚举——但要注意它真的无法同时获取和保存!
{
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;
}
当您的用户直接访问其中一个资源时,这也自然有效。例如如果他们可以导航到单个待办事项,您可以将其填充到 todosById
中并同样跟踪其状态。如果您稍后还加载了待办事项列表,则来自服务器的最新值将合并到我们之前加载的待办事项中。
在客户端处理新资源的创建时,在将其保存到服务器之前,您可能还没有它的 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
}
}
}
分离状态
更复杂的资源请求管理的另一种选择是使其完全独立并且与资源无关fetching/saving/etc。
与此类似的内容:
{
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 创建了库,但我没有使用过那个特定的库,所以我不能多说。我使用自己制作的自定义抽象。
假设我有一个管理待办事项的应用程序,用户可以通过单击按钮添加新的待办事项。
当用户单击按钮时,将出现一个微调器,直到将待办事项保存到后端或请求失败。
当按钮被按下时,一个名为 ADD_TODO_REQUEST 的动作被调度,然后将被 redux-observable 史诗拦截,该史诗将执行 HTTP 请求保存待办事项,并将根据 HTTP 请求结果分派 ADD_TODO_COMPLETE 操作或 ADD_TODO_FAILED 操作。
应用程序应在 "add todo" 按钮旁边显示一个微调器。
为此,我的状态包含一个名为 isSaving 的标志,当 HTTP 请求挂起时该标志将设置为 true,当 HTTP 请求完成时将重置为 false。
我的初始状态形状是这样的:
{
todos: [],
isSaving: false
}
当应用程序启动时,将调度的第一个操作是 FETCH_TODO_REQUEST,它将调用另一个 API 端点以获取所有待办事项。
应用程序应该再一次显示一个微调器来通知用户正在下载待办事项,为此我在状态中添加了另一个标志,称为 isFetching。
这个新标志是必需的,因为如果我在添加待办事项时共享相同的标志,我会在用户仅添加待办事项时为整个应用程序显示一个微调器。
我的初始状态形状现在看起来像这样:
{
todos: [],
isSaving: false,
isFetching: false
}
这种方法对我来说看起来不错,但是如果用户也可以删除待办事项,我必须跟踪这个额外的 HTTP 请求状态,因此我需要添加另一个标志(可能称为 isDeleting ) 状态。
请注意,我想在 "add button" 旁边显示一个微调器,并在每个要删除的待办事项旁边显示一个微调器。这些微调器可以同时出现,这就是为什么一个标志是不够的,我已经采用了这种方法。
在我可能有许多不同并发的情况下 API "actions" 我需要为每个可能的请求设置一个标志。
如果我还想显示错误,我现在需要为每个可用的 API "action" 设置两个属性:一个表示请求正在进行,另一个表示错误 object。
这种方法的问题在于它看起来非常、非常、冗长。
是否有一种惯用且更智能的方法来跟踪并发 http 请求的状态?
为触及相同 "entity" 的每个可能的 http 请求设置一个标志是否正确?
为什么商店中没有一个 isBusy
值?然后你的减速器根据 ADD_TODO_..
、DELETE_TO_DO_..
等切换它。这假设您只显示一个忙碌指示器。
我见过很多处理这个问题的方法,各有优缺点。
每个资源都有自己的状态
虽然没有规则,但通常建议 redux 用户将他们的状态模式建模为数据库。
这主要意味着您将存储由某个 ID 索引的资源。在这种情况下,您有一个待办事项列表,因此您的状态可能如下所示:
{
todosById: {
'1': {
id: '1',
content: 'Do something'
},
'2': {
id: '2',
content: 'Do another thing'
}
}
}
当你想要一个包含所有待办事项的数组时,只要你 select 你的状态:
就可以即时创建它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 查找内容变得非常容易,这在用户流程中经常需要;例如显示事物列表然后使用 selects 其中之一。
适用于您描述的情况的另一个原因是我们现在可以单独跟踪每个资源的状态。所以每个人都可以有自己的 isFetching
、isSaving
等。或者,如果你想保证资源只处于单一状态,你可以使用枚举——但要注意它真的无法同时获取和保存!
{
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;
}
当您的用户直接访问其中一个资源时,这也自然有效。例如如果他们可以导航到单个待办事项,您可以将其填充到 todosById
中并同样跟踪其状态。如果您稍后还加载了待办事项列表,则来自服务器的最新值将合并到我们之前加载的待办事项中。
在客户端处理新资源的创建时,在将其保存到服务器之前,您可能还没有它的 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
}
}
}
分离状态
更复杂的资源请求管理的另一种选择是使其完全独立并且与资源无关fetching/saving/etc。
与此类似的内容:
{
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 创建了库,但我没有使用过那个特定的库,所以我不能多说。我使用自己制作的自定义抽象。