使用TanStack Query构建可扩展的React应用

本文详细介绍了如何使用TanStack Query(原React Query)管理React应用中的数据获取和状态。通过useQuery实现GET请求缓存和重试机制,useMutation处理POST/PUT/DELETE操作,并展示其在大型应用中减少样板代码、提升开发效率的实际价值。

使用TanStack Query构建可扩展的React应用

通过内置缓存、重试和后台重新获取等功能,TanStack Query让大规模React应用中的CRUD操作不再痛苦。

作者:Poornakumar Rasiraju
发布日期:2025年9月24日
类型:技术分析

当构建React应用时,数据获取通常从原生fetch API或Axios等工具开始。虽然这种方法适用于小型项目,但大型应用需要缓存、重试、同步和请求取消等功能,而这正是TanStack Query(原React Query)的强项。它为CRUD操作提供了经过实战检验的抽象,并内置了强大的状态管理功能。

本文将逐步介绍如何使用useQuery获取数据、使用useMutation执行变更操作,并重点介绍使TanStack Query成为扩展React应用的有力工具的一些特性。

使用useQuery获取数据(GET)

useQuery钩子专为GET API调用设计。以下是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const fetchMovies = async () => {
  const response = await fetch('/v1/api/movies');
  if (!response.ok) throw new Error('Failed to fetch movies');
  return response.json();
};

export const useFetchMoviesList = () => {
  return useQuery({
    queryKey: ['movies_list'],
    queryFn: fetchMovies,
    retry: 3,
  });
};

在此示例中,fetchMovies函数从’/v1/api/movies’请求或获取数据,如果响应失败则抛出错误,成功时返回JSON数据。

我们将其包装在自定义钩子useFetchMoviesList中,该钩子使用useQuery。queryKey用于标识请求以进行缓存,queryFn定义如何获取数据,retry: 3表示对失败的请求最多重试三次。

useFetchMoviesList钩子可以在MovieList组件中如下使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const MoviesList = () => {
  const { data, isLoading, isError } = useFetchMoviesList();

  if (isLoading) return <span>Loading...</span>;
  if (isError) return <span>Something went wrong</span>;

  return (
    <ul>
      {data.map((movie: { id: number; title: string }) => (
        <li key={movie.id}>{movie.title}</li>
      ))}
    </ul>
  );
};

在MoviesList组件内部,该钩子提供了内置状态,包括isLoading、isError和data。有了这些状态,您不再需要手动管理useState或useEffect,因为TanStack Query会为您处理它们。该组件在API调用运行时显示加载消息,在重试后请求失败时显示错误消息,并在数据成功加载后显示电影列表。

使用useMutation执行变更操作

useMutation钩子专为POST/PUT/DELETE API调用设计。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const addMovie = async (movieId: string) => {
  const response = await fetch('/v1/api/movies', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ id: movieId }),
  });
  if (!response.ok) throw new Error('Failed to add movie');
  return response.json();
};

export const useAddMovie = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addMovie,
    onSuccess: () => {
      // 获取更新后的电影列表,因为我们添加了一部电影
      queryClient.invalidateQueries({ queryKey: ['movies_list'] });
    },
  });
};

在此示例中,addMovie函数向’v1/api/movies’发送POST请求,请求体中包含新电影的ID。如果响应失败,则抛出错误;否则返回解析后的JSON响应。

然后我们将其包装在自定义钩子useAddMovie中,该钩子使用useMutation。mutationFn定义如何执行变更,onSuccess回调调用invalidateQueries来刷新缓存的movies_list查询。这确保在添加电影后立即更新UI以反映更新后的列表。

useAddMovie钩子可以在AddMovieButton组件中如下使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const AddMovieButton = () => {
  const { mutate, isLoading, isError } = useAddMovie();

  const handleAddMovie = () => {
    mutate('123');
  };

  return (
    <section>
      <button onClick={handleAddMovie} disabled={isLoading}>
        {isLoading? 'Adding...': 'Add Movie'}
      </button>

      {isError && <span>Failed to add movie</span>}
    </section>
  );
};

AddMovieButton组件利用useAddMovie钩子来管理新电影的添加。在组件内部,它从钩子中提取mutate、isLoading和isError。

handleAddMovie函数调用mutate(‘123’),触发添加ID为'123’的电影的变更操作。当请求进行时,按钮变为禁用状态并显示’Adding…’。如果请求失败,组件显示错误消息。否则,一旦成功,TanStack Query会自动刷新电影列表。

简而言之,该组件提供了一种清晰的方式来添加电影,向用户显示加载和错误状态的实时反馈,而无需额外的状态管理代码。

注意:要使用useQuery和useMutation,您的组件树必须包装在QueryClientProvider中。这为TanStack Query设置了客户端上下文。

TanStack Query消除了样板代码,并提供了内置的重试、重新获取、错误处理和缓存功能,使开发人员感觉更高效、更有生产力。它还具有以下特性,可有效管理服务器状态:

  • 重试逻辑:配置重试(例如retry: 3),使临时性故障不会中断用户流程。
  • 条件获取:将enabled设置为false,使查询等待直到满足所需条件。
  • 缓存:TanStack Query在第一次获取后缓存数据,并在组件之间共享,减少重复请求。
  • 请求取消:如果查询不再需要(例如当组件卸载或用户导航离开时),中止查询。
  • 后台重新获取:当用户重新聚焦浏览器或重新连接到网络时,TanStack Query自动保持数据新鲜。

TanStack Query在大型应用中非常有用,因为它消除了重复的样板代码,降低了不一致性的风险,并提供了生产级功能,如开箱即用的缓存和同步。然而,对于仅进行少量API请求的小型应用,普通的fetch可能就足够了。在这些情况下,引入TanStack Query可能会增加不必要的包大小而无法带来显著好处。

结论

TanStack Query通过提供声明式数据获取、缓存、重试和变更操作,已成为开发人员处理复杂应用的良好工具。对于大规模React应用,采用此工具有助于构建可靠且可维护的系统。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计