首页 / 文章 / 什么是 React Query?以及如何在生产环境中使用它?

什么是 React Query?以及如何在生产环境中使用它?

面向生产系统的《什么是 React Query?以及如何使用它?》操作指南:为采用该模式的团队提供的契约、检查项及可直接插入的代码片段。

2930 词

以下笔记为“从 React Query 入手”提供了实用的学习路径。重点在于契约、校验以及可直接插入的代码占位符,而非激励性表述。 在完成概览阶段时,首先明确契约内容:所需的输入参数、成功信号,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将此阶段视为输入与经过验证的输出之间的契约。为相关产物命名,定义成功校验标准,并杜绝无声的半完成状态。

服务器状态与客户端状态

将服务器状态与客户端阶段视为可测量的指标,这样才能达到最佳效果。在扩大范围之前,先记录一份理想的运行日志、一个故障案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。尽量保持渲染操作的成本较低,只有在经过测量后,才将高成本的计算过程放在记忆化机制之后处理。过早使用记忆化可能会掩盖过时的属性错误。

传统上,我们就是这样获取数据的

传统上,将舞台视为可测量的界面时,最佳处理方式便是如此:在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。配置应置于应用程序代码之外,环境文件、密钥存储和功能标志应集中存放于一个位置,以便操作人员无需查看整个结构即可进行审计。尽量降低渲染成本,只有在经过测量后才会将耗时的计算操作放入缓存机制中;过早使用缓存可能会掩盖过时属性带来的错误。

const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
  async function fetchUsers() {
    try {
      setLoading(true)      const res = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      )      const data = await res.json()      setUsers(data)
    } catch (err) {
      setError(err)
    } finally {
      setLoading(false)
    }
  }  fetchUsers()

使用 React Query 后,上述代码变为:

在使用 React Query 时,若能将其视为可度量的界面,效果会最佳。在扩大范围之前,先记录一个理想状态下的运行日志、一个失败案例以及回滚说明。 同时文档化正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的功能。 尽量降低渲染成本,只有在经过测量后才将耗时的计算操作放在记忆化机制之后处理。过早使用记忆化可能会掩盖属性过时的问题。

function Users() {
  const {
    data,
    isLoading,
    error
  } = useQuery({
    queryKey: ["users"],
    queryFn: fetchUsers
  })
  if (isLoading) {
    return <p>Loading...</p>
  }  if (error) {
    return <p>Something went wrong</p>
  }  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  )
}

在使用 React Query 时,若能将其视为可度量的界面,效果会最佳。在扩大范围之前,先记录一个理想状态下的运行日志、一个失败案例以及回滚说明。 应将这一阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,杜绝无声的半完成状态。

那么,我们究竟该如何使用它呢?

那么在修改代码之前,我们该如何规划流程、明确输入参数、确定各步骤的负责人以及退出标准呢?操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及令牌或查询成本。提前了解这些成本信息,就能避免在从演示环境过渡到共享环境时出现意外费用。应将状态与负责数据变更的组件放在一起处理;若将所有数据都存放在全局存储中,就很难发现与时间相关的错误。

1. 设置

在1号设置阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 状态信息应与负责执行变更的组件放在一起。将所有数据都存放在全局存储中会使得时序错误更难被发现。

npm install @tanstack/react-query
const queryClient = new QueryClient();

root.render(
<QueryClientProvider client={queryClient}>
    <App />
</QueryClientProvider>
);

2. 使用useQuery获取数据

对于“按阶段获取数据”这一功能,修改代码之前需先明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 需同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及错误处理都是产品功能的一部分,而非后续需要补充的内容。 应将状态信息与负责数据变更的组件放在一起管理。若将所有数据都存放在全局存储中,会导致时序相关的问题更难被发现。 对于“按阶段获取数据”这一功能,修改代码之前需先明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 应将这一阶段视为输入与验证后输出之间的契约。为相关数据命名,明确成功标准,杜绝无声的半完成状态。

const { data,
isPending,
error } = useQuery({
 queryKey: ["users"],
 queryFn: fetchUsers
 })

2.1. 查询函数

在处理“2.1 查询函数”阶段时,首先需明确功能规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改始终符合预期。 在功能结果旁记录执行时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。 应将各种效果视为与外部世界的同步机制,而非渲染过程中衍生值的替代品。

async function fetchUsers() {
  const res = await fetch("/api/users")

  if (!res.ok) {
    throw new Error("Failed to fetch users")
  }

  return res.json()
}
useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers
})

2.2. 查询键

在处理 2×2 查询键阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 应将效果处理视为与外部世界的同步机制,而非渲染过程中衍生值的替代品。

3. 缓存

在处理三个缓存阶段时,首先需明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 同时记录正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续的优化工作。 应将各种效应视为与外部世界的同步操作,而非渲染过程中衍生值的替代品。 在处理三个缓存阶段时,首先需明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 要把这个阶段视为输入与经过验证的输出之间的契约。为相关产物命名,定义成功检测标准,并拒绝默许的部分完成状态。

4. staleTime

将staleTime的四个阶段视为可测量的指标时,其效果最佳。在扩大范围之前,先记录一份理想的测试用例、一个故障案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解这些成本,就能避免在从演示环境过渡到共享环境时出现意外的费用支出。尽量保持渲染工作的成本较低,只有在经过测量之后才将高成本的计算操作放在记忆化机制之后处理。过早进行记忆化可能会掩盖与过时属性相关的错误。

useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  staleTime: 60,000 // 60 seconds
})

为什么我们需要staletime?

将“为何需要阶段化处理”视为一个可测量的指标来分析效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 将配置信息与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放,这样操作人员无需查看整个系统结构即可进行审计。 尽量降低渲染成本,只有在经过测量后才会将耗时的计算操作放入缓存机制中。过早使用缓存可能会掩盖属性过时的问题。

理解 staleTime

将Understanding staleTime阶段视为可测量的对象来处理时,其效果最佳。在扩大范围之前,先记录一个理想状态下的流程示例、一个故障案例以及回滚说明。 同时记录正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的功能。 尽量降低渲染成本,只有在经过测量后才考虑通过记忆化手段来处理那些计算成本较高的操作。过早使用记忆化可能会掩盖属性过时的问题。 将Understanding staleTime阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,绝不允许出现无声的、不完整的处理结果。

User visits page
       ↓
Fetch users
       ↓
User navigates away
       ↓
User comes back
       ↓
Fetch users again
staleTime: 5 * 60 * 1000

5. gcTime

在5个gcTime阶段中,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及令牌或查询成本。提前显示成本信息,可避免在从演示环境切换到共享环境时出现意外费用。应将状态与负责数据变更的组件放在一起处理;若将所有数据都存放在全局存储中,就会使时间相关的问题更难被发现。

useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  gcTime: 60000
})

staleTime和gcTime有什么区别?

在“差异是什么”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 状态信息应与负责执行变更的组件放在一起。将所有数据都存放在全局存储中会使得时序错误更难被发现。

6. 重新获取数据

在6个重新获取数据阶段中,修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 状态应与负责数据变更的组件放在一起管理。将所有数据都存放在全局存储中会使得时序相关的问题更难被发现。 在6个重新获取数据阶段中,修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将这一阶段视为输入与已验证输出之间的契约。为相关产物命名,明确成功判定标准,杜绝无声的半完成状态。

const { refetch } = useQuery(...)
refetch()
useQuery({
  queryKey: ["users"],
  queryFn: fetchUsers,
  refetchInterval: 30,000
})

7. 变更操作——创建、更新、删除

在处理7种变更操作的创建与更新阶段时,首先需明确合同规范:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。 应将这些效果视为与外部世界的同步机制,而非渲染过程中派生值的替代品。

async function createUser(user) {
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/users",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(user),
    }
  );

  if (!response.ok) {
    throw new Error("Failed to create user");
  }

  return response.json();
}
import { useMutation } from "@tanstack/react-query";

function CreateUser() {
  const mutation = useMutation({
    mutationFn: createUser,
  });

  return (
    <button
      onClick={() =>
        mutation.mutate({
          name: "John Doe",
          email: "john@example.com",
        })
      }
      disabled={mutation.isPending}
    >
      {mutation.isPending ? "Creating..." : "Create User"}
    </button>
  );
}

export default CreateUser;
Button click
    ↓
mutation.mutate(user)
    ↓
createUser(user)
    ↓
POST request
    ↓
Server

7.1. 变更操作状态

在处理7个1变异状态阶段时,首先写下契约内容:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 应将效果处理视为与外部世界的同步机制,而非渲染过程中替代派生值的手段。

const {
  mutate,
  isPending,
  isSuccess,
  isError,
  error,
  data,
} = useMutation({
  mutationFn: createUser,
});
<button
  onClick={() => mutate({ userName: "delfina ghimire" })}
  disabled={isPending}
>
  {isPending ? "Creating..." : "Create user"}
</button>

8. 查询客户端

在完成8个查询客户端阶段的工作时,首先需明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续的优化工作。 应将各种效应视为与外部世界的同步操作,而非渲染过程中衍生值的替代品。 在完成8个查询客户端阶段的工作时,首先需明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与经过验证的输出之间的契约。为相关产物命名,定义成功检测标准,并拒绝默许的部分完成状态。

9. 查询失效处理

将“9个查询失效阶段”视为可度量的指标时,其效果最佳。在扩大范围之前,先记录一份理想的测试用例、一个故障案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。尽量保持渲染工作的成本较低,只有在经过测量后才将高成本的计算操作放入缓存机制中。过早使用缓存可能会掩盖过时的属性错误。

[
  { id: 1, userName: "delfina ghimire" },
  { id: 2, userName: "spiderman ghimire" },
]
mutation.mutate({
  userName: "ironman ghimire",
});
const queryClient = useQueryClient();
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ["users"],
    });
  },
});
Create Users (Mutation)
     ↓
Server data changes
     ↓
Invalidate ["users"]
     ↓
Query becomes stale
     ↓
Refetch
     ↓
UI gets fresh data

失效处理与重新获取

将“失效处理”与“重新获取数据”阶段视为可度量的对象来管理,效果最佳。在扩大范围之前,先记录一份理想的运行日志、一个故障案例以及回滚说明。 配置应置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放,以便操作人员无需查看整个系统结构即可进行审计。 尽量降低渲染成本,只有在经过测量后,才将耗时的计算操作放在记忆化机制之后处理。过早使用记忆化可能会掩盖过时属性带来的错误。

结论

将“结论阶段”视为可度量的对象来处理效果最佳。在扩大范围之前,需记录一个理想案例、一个失败案例以及回滚说明。 同时记录正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续的优化工作。 尽量降低渲染成本,只有在经过测量后才考虑通过记忆化手段处理高成本的推导过程。过早使用记忆化可能会掩盖过时的属性错误。 将“结论阶段”视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,绝不允许出现无声的半完成状态。

简而言之

在简述阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。还需在功能结果旁记录执行时间以及令牌或查询成本。提前了解这些成本信息,可避免在从演示环境过渡到共享环境时出现意外费用。

操作检查清单

在操作检查清单阶段,同样需要在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。

应优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤出错时,故障应指向单一的责任模块,而非复杂的处理流程。

将状态与负责数据变更的组件放在一起。若将所有数据都存放在全局存储中,时序错误就更难被发现。

编写简短的操作手册:说明如何轮换密钥、如何清空队列、以及如何回滚上一次的数据导入操作。

将此阶段视为输入数据与经过验证的输出结果之间的契约。为相关产物命名,明确成功标准,杜绝无声的半完成状态。

将状态与负责数据变更的组件放在一起。若将所有数据都存放在全局存储中,时序错误就更难被发现。

在推广该技术栈之前,应先冻结版本,为关键流程记录标准输出日志,并明确回滚步骤。共享环境需要设置速率限制、租户验证机制,以及负责密钥轮换的明确责任人。与其展示花哨的一次性演示,不如注重扎实的可靠性。

针对1aa45226c385的批处理说明:请将提供商密钥存放在仓库之外,设定单会话令牌上限,并将日志存储在评估用示例文件旁,以便后续模型更换时仍能保持对比性。