react query(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言

在现代前端开发中,数据管理始终是一个核心挑战。无论是获取数据、缓存、状态同步,还是处理错误,开发者常常需要编写大量重复代码。React Query 的出现,通过一套简洁的设计理念和强大的功能,彻底改变了这一局面。它不仅简化了数据获取与状态管理的复杂度,还提供了开箱即用的缓存策略、自动重试、分页支持等特性。本文将从零开始,逐步解析 React Query 的核心概念、使用场景及实际案例,帮助开发者快速掌握这一工具。


一、React Query 是什么?为什么需要它?

1.1 核心概念

React Query 是一个用于数据获取和状态管理的开源库,专为现代前端框架(如 React)设计。它的核心目标是减少开发者在数据管理上的重复劳动,并提供以下核心能力:

  • 自动缓存:智能管理数据生命周期,避免重复请求。
  • 状态同步:通过全局状态树保持数据一致性。
  • 错误处理:内置重试机制和错误追踪。
  • 分页与无限滚动:支持复杂数据流的高效处理。

比喻:如果将数据获取比作图书馆借书,React Query 就像一个智能图书管理员。它记住你之前借过的书(缓存),自动更新过期的书籍信息(失效时间),并确保所有借阅记录(状态)始终一致。

1.2 为什么选择 React Query?

在没有 React Query 的情况下,开发者需要手动处理以下问题:

  • 手动管理 isLoadingisErrordata 等状态。
  • 编写复杂的缓存逻辑,例如设置过期时间或版本控制。
  • 处理组件卸载时的请求清理。
  • 实现数据的自动重试或网络恢复时的同步。

而 React Query 通过以下特性解决了这些问题:

  • 声明式 API:通过 useQueryuseMutation 等 Hook,将数据获取与组件状态无缝结合。
  • 全局状态管理:所有查询和突变(mutations)由 React Query 的客户端统一管理,无需额外搭建状态管理框架。
  • 社区与生态支持:作为开源项目,React Query 拥有活跃的社区和完善的文档,兼容主流工具链(如 TypeScript、React Native)。

二、快速上手:第一个 React Query 示例

2.1 安装与基础配置

在 React 项目中,通过以下命令安装 React Query:

npm install @tanstack/react-query  

然后在根组件中初始化客户端:

import { ReactQueryClientProvider, QueryClient } from '@tanstack/react-query';  

function App() {  
  const queryClient = new QueryClient();  
  return (  
    <ReactQueryClientProvider client={queryClient}>  
      {/* 应用内容 */}  
    </ReactQueryClientProvider>  
  );  
}  

2.2 使用 useQuery 获取数据

以下是一个简单的示例,展示如何通过 useQuery 获取用户列表:

import { useQuery } from '@tanstack/react-query';  

function UserList() {  
  const { data, isLoading, isError } = useQuery({  
    queryKey: ['users'], // 唯一标识符,用于缓存  
    queryFn: async () => {  
      const response = await fetch('/api/users');  
      return response.json();  
    },  
  });  

  if (isLoading) return <div>Loading...</div>;  
  if (isError) return <div>Error fetching data</div>;  

  return (  
    <ul>  
      {data.map(user => (  
        <li key={user.id}>{user.name}</li>  
      ))}  
    </ul>  
  );  
}  

关键点解析

  • queryKey 是查询的唯一标识,用于缓存和更新管理。
  • queryFn 是执行异步操作的函数,返回数据或抛出错误。
  • useQuery 返回对象包含 dataisLoadingisError 等状态,直接用于组件渲染。

三、核心功能详解

3.1 缓存与失效策略

React Query 的缓存机制基于 queryKeystaleTime(过期时间)。默认情况下,缓存会在 staleTime(默认 0)后标记为“过期”,但只有在重新渲染时才会触发重新获取数据。

案例:假设用户列表每 5 分钟更新一次:

useQuery({  
  queryKey: ['users'],  
  queryFn: fetchUsers,  
  staleTime: 5 * 60 * 1000, // 5分钟  
});  

比喻:缓存就像冰箱里的食物标签。超过保质期(staleTime)的食物会被标记为“可能过期”,但只有当你打开冰箱(重新访问数据)时才会决定是否需要重新获取(扔掉旧食物,买新食物)。

3.2 自动重试与网络恢复

通过 retryretryDelay 配置,React Query 可以自动重试失败的请求。例如:

useQuery({  
  queryKey: ['users'],  
  queryFn: fetchUsers,  
  retry: 3, // 最多重试3次  
  retryDelay: (attemptNumber) => attemptNumber * 1000, // 指数级延迟  
});  

3.3 突变(Mutations)与数据更新

useMutation 用于处理数据提交(如创建、更新、删除),并管理突变后的状态:

import { useMutation } from '@tanstack/react-query';  

function CreateUserForm() {  
  const mutation = useMutation({  
    mutationFn: async (newUser) => {  
      const response = await fetch('/api/users', {  
        method: 'POST',  
        body: JSON.stringify(newUser),  
      });  
      return response.json();  
    },  
  });  

  const handleSubmit = async (event) => {  
    event.preventDefault();  
    const formData = new FormData(event.target);  
    const user = Object.fromEntries(formData.entries());  

    try {  
      await mutation.mutateAsync(user);  
      alert('User created successfully!');  
    } catch (error) {  
      alert('Error creating user');  
    }  
  };  

  return (  
    <form onSubmit={handleSubmit}>  
      {/* 表单字段 */}  
      <button type="submit">Create User</button>  
    </form>  
  );  
}  

四、进阶用法与性能优化

4.1 分页与无限滚动

通过 useInfiniteQuery 处理分页数据,例如:

const { data, fetchNextPage } = useInfiniteQuery({  
  queryKey: ['posts'],  
  queryFn: ({ pageParam = 1 }) => fetch(`/api/posts?page=${pageParam}`),  
  getNextPageParam: (lastPage) => lastPage.nextPage, // 根据 API 响应决定是否继续分页  
});  

// 触发加载下一页  
<button onClick={() => fetchNextPage()}>Load More</button>;  

4.2 全局状态与依赖注入

React Query 支持通过 context 实现跨组件共享状态。例如,在父组件中预加载数据:

function ParentComponent() {  
  const { data: users } = useQuery({ queryKey: ['users'], queryFn: fetchUsers });  

  return (  
    <ChildComponent users={users} />  
  );  
}  

4.3 性能优化技巧

  • 避免重复查询:确保 queryKey 的唯一性,避免因键冲突导致重复请求。
  • 合理设置 staleTime:根据业务需求调整缓存有效期,平衡性能与数据新鲜度。
  • 使用 keepPreviousData:在重新获取数据时,临时保留旧数据,避免界面闪断:
    useQuery({  
      queryKey: ['users'],  
      queryFn: fetchUsers,  
      keepPreviousData: true, // 保留旧数据直到新数据到达  
    });  
    

五、实际案例:构建一个完整的 CRUD 应用

5.1 场景描述

假设我们要构建一个用户管理界面,包含以下功能:

  • 获取用户列表。
  • 创建新用户。
  • 更新用户信息。
  • 删除用户。

5.2 完整代码示例

// 用户列表组件  
function UserList() {  
  const { data: users = [] } = useQuery({  
    queryKey: ['users'],  
    queryFn: async () => {  
      const response = await fetch('/api/users');  
      return await response.json();  
    },  
  });  

  return (  
    <div>  
      <UserTable users={users} />  
    </div>  
  );  
}  

// 创建用户表单  
function CreateUserForm() {  
  const mutation = useMutation({  
    mutationFn: async (user) => {  
      const response = await fetch('/api/users', {  
        method: 'POST',  
        headers: { 'Content-Type': 'application/json' },  
        body: JSON.stringify(user),  
      });  
      return await response.json();  
    },  
    onSuccess: () => {  
      // 成功后刷新用户列表  
      queryClient.invalidateQueries({ queryKey: ['users'] });  
    },  
  });  

  const handleSubmit = (event) => {  
    event.preventDefault();  
    const formData = new FormData(event.target);  
    const user = Object.fromEntries(formData.entries());  
    mutation.mutate(user);  
  };  

  return (  
    <form onSubmit={handleSubmit}>  
      <input name="name" placeholder="Name" required />  
      <input name="email" placeholder="Email" required />  
      <button type="submit">Create User</button>  
    </form>  
  );  
}  

// 更新用户操作  
function EditUser({ user }) {  
  const mutation = useMutation({  
    mutationFn: async (updatedUser) => {  
      const response = await fetch(`/api/users/${user.id}`, {  
        method: 'PUT',  
        headers: { 'Content-Type': 'application/json' },  
        body: JSON.stringify(updatedUser),  
      });  
      return await response.json();  
    },  
    onSuccess: () => {  
      queryClient.invalidateQueries({ queryKey: ['users'] });  
    },  
  });  

  // 表单逻辑与 mutation.mutate 结合  
}  

// 删除用户操作  
function DeleteUser({ user }) {  
  const mutation = useMutation({  
    mutationFn: async () => {  
      await fetch(`/api/users/${user.id}`, { method: 'DELETE' });  
    },  
    onSuccess: () => {  
      queryClient.invalidateQueries({ queryKey: ['users'] });  
    },  
  });  

  return (  
    <button onClick={() => mutation.mutate()}>Delete</button>  
  );  
}  

5.3 关键点总结

  • 数据刷新:通过 invalidateQueries 强制重新获取数据,确保 UI 与后端状态一致。
  • 错误隔离:每个 useMutation 可独立处理错误,避免影响其他功能。
  • 组件复用:通过解耦查询与突变逻辑,提升代码可维护性。

六、结论

React Query 通过其简洁的 API 和强大的功能,重新定义了现代前端数据管理的范式。它不仅降低了开发者处理复杂场景的门槛,还通过内置的缓存、重试、分页等特性,显著提升了应用的性能和用户体验。对于刚入门的开发者,建议从基础查询开始,逐步探索突变、分页等高级功能;对于中级开发者,可以结合实际项目,利用 React Query 的全局状态管理和性能优化技巧,构建更高效、可靠的前端应用。

随着前端技术的不断演进,React Query 的生态也在持续扩展(例如与 Next.js 的深度集成)。掌握这一工具,无疑是开发者在数据管理领域迈向专业化的关键一步。

最新发布