如何检查React Query的请求状态

React Query使用stale-while-revalidate缓存机制,并且存在后台刷新数据的机制。因此状态检查就变的非常重要。本文主要说明后台刷新数据时,会带来的一些问题和解决方法。

原文地址: Status Checks in React Query

React Query的一个优点是可以轻松访问查询的状态字段。我们可以立即知道我们的查询是否正在加载或是否有错误。为此,该库公开了一堆布尔标志,这些标志主要来自内部状态机。可以在这里看到这些状态的类型声明,我们的查询可能处于以下状态之一:

  • success:我们的请求已经成功,我们可以读取这个请求的 data 字段
  • error:我们的请求出现了问题,因此 error 字段被设置了
  • loading:我们的请求还没有数据,目前正在做首次的 loading
  • idle:我们的请求尚未被执行过,因为它还没有被设置 enabled

注意,isFetching这个标记,并不是内部状态机器的一部分,它是一个额外的标记,用来让我们知道一个请求正在执行。我们可以是获取和成功,我们也可以是获取和错误——但我们不能同时加载和成功。因为这就是状态机器的本身行为。

来个常见的例子

idle状态是非常不常见的,因为它是禁用查询的情况是非常少见。所以大多数示例看起来像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const todos = useTodos()

if (todos.isLoading) {
  return 'Loading...'
}

if (todos.error) {
  return 'An error has occurred: ' + todos.error.message
}

return <div>{todos.data.map(renderTodo)}</div>

在这里,我们首先检查加载和错误状态,然后显示我们的数据。这对于绝大部分用例可能没问题,但对于其他用例则不行。许多数据获取解决方案,尤其是手工制作的解决方案,没有重新获取机制,或者只在用户发出明确的指令时才进行重新获取。

但是React Query会自动获取。

默认情况下,它会非常积极地重新获取,并且无需用户主动请求重新获取。_refetchOnMount_ 、_refetchOnWindowFocus_ 和 refetchOnReconnect 的概念非常适合保持我们的数据准确,但如果此类自动后台重新获取失败,它们可能会导致令人困惑的用户体验。

后台错误

在许多情况下,如果后台重新获取失败,它可以被默默地忽略。 但是上面的代码并没有这样做。 让我们看两个例子:

  • 当用户打开一个页面,首次查询成功后。用户在页面上工作了一段时间,然后切换浏览器选项卡以查看电子邮件。几分钟后用户又回来了,React Query将进行后台重新获取。但是这个请求失败了。
  • 我们的用户在具有列表视图的页面上,单击一项以深入查看详细信息视图。该操作执行的非常顺利,因此他们返回到列表视图。一旦用户再次进入详细视图,他们将看到缓存中的数据。这非常好——除非后台重新获取失败。

在这两种情况下,我们的查询都将处于以下状态:

1
2
3
4
5
{
  "status": "error",
  "error": { "message": "Something went wrong" },
  "data": [{ ... }]
}

如我们所见,我们将同时获得错误信息和陈旧数据。这就是React Query非常实用的原因——它包含stale-while-revalidate缓存机制,这意味着它总是会在数据存在时为我们提供数据,即使这是陈旧的数据。

现在由我们来决定展示什么。显示错误很重要吗? 如果我们有陈旧数据,仅显示陈旧数据就足够了吗? 我们是否应该同时显示两者,也许带有指出后台获取发生错误的指示信息?

这个问题没有明确的答案——这取决于我们具体的应用场景。但是,鉴于上面的两个示例,我认为如果将数据替换为错误屏幕,那将是一种有点令人困惑的用户体验。

当我们考虑到React Query默认情况下会使用指数退避重试失败的查询3次时,这就变得很重要了,因此可能需要几秒钟的时间才能将过时的数据替换为错误屏幕。 如果我们没有后台获取提供进度条,这可能真的很令人困惑。

这就是我通常首先检查数据可用性的原因:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const todos = useTodos()

if (todos.data) {
  return <div>{todos.data.map(renderTodo)}</div>
}
if (todos.error) {
  return 'An error has occurred: ' + todos.error.message
}

return 'Loading...'

同样,没有哪一个原则是绝对正确的,因为它高度依赖于实际情况。 每个人都应该意识到主动重新获取的后果,我们必须相应地构建我们的代码,而不是严格遵循简单的待办事项示例😉。

特别感谢Niek Bosch,他首先发现并向我展示了为什么这种状态检查模式在某些场景下是不适用的,甚至是有害的。