TanStack Query(React Query) 使用总结

目录

一、概述

主要功能

核心价值

[与 React Query 的关系](#与 React Query 的关系)

使用步骤

二、核心概念

1、useQuery

queryKey

queryFn

enabled

retry

staleTime

gcTime

返回结果

2、useMutation

3、失效策略

失效并重新获取方式

乐观更新方式

缓存

1、缓存查询

全局配置缓存

各项配置详解

[gcTime(原 cacheTime,v4.28.0+)](#gcTime(原 cacheTime,v4.28.0+))

staleTime

针对单个查询配置

更精细的缓存控制

组合配置示例

2、缓存失效策略

主动失效缓存

3、设置缓存数据

4、移除缓存数据

[5、 实用配置建议](#5、 实用配置建议)

异常处理

核心要点总结:

错误重试案例

基础用法

简单错误显示

重试策略

全局配置(推荐)

实用错误处理函数

错误抛出案例

增删改副作用

1、基础使用

2、核心状态和函数

3、实用示例

[1. 表单提交](#1. 表单提交)

[2. 带变量的更新操作](#2. 带变量的更新操作)

[3. 结合查询更新(自动重新获取)](#3. 结合查询更新(自动重新获取))

4、常用配置选项

结合Typescript语法

[查询 Hook](#查询 Hook)

[修改 Hook](#修改 Hook)


一、概述

官方文档:Installation | TanStack Query React Docs

主要功能

React Query 是一个专门用于数据获取、缓存、同步和更新的 React 库。

简单来说,这个库是专用用来维护接口数据的,类比可以参考Redux是专门做状态管理的库。

Redux 使用 Thunk 管理异步接口时,虽然能够实现功能,但相较于 React Query,往往需要编写更多代码,并且可能需要借助复杂的 Reselect 来实现缓存机制。而 React Query 在设计上更为简洁易维护,它提供了自动化的异步状态管理与缓存功能,从而显著提升了开发效率与应用的可维护性。

让Redux专注于管理状态,让React Query专注于管理接口。

核心价值

1、自动化加载和异常处理,减少样板代码

2、智能缓存和后台更新,提升性能和用户体验

3、将复杂的异步逻辑简化为声明式的Hooks;

4、提供强大的开发者工具便于调试;

5、是专用的服务器状态管理库

与 React Query 的关系

React Query 是 v4 以前的叫法,从 v4 起就叫 TanStack Query。之所以改名字,是因为这个团队这套方案推广到除 React 之外的其他框架中去。到目前(2025年5月)最新的 v5 版本已经支持 React、Vue、Angular、Solid、Svelte 5 大框架。

使用步骤

1、创建一个ReactQuery实例;

2、导入QueryClientProvider组件;

3、用Provider包裹应用;

4、将client实例传递给Provider。

这里的Provider就是使用了React的Context来进行全局状态共享,让每个内部组件都可以通过useQuery获取到唯一的数据源。

javascript 复制代码
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// 1、在组件外实例化全局实例,用于数据共享
const queryClient = new QueryClient()

// 2、使用Provider包裹全局组件
<QueryClientProvider client={queryClient}>
    <App/>
</QueryClientProvider>

// 3、调useQuery调用网络请求
const { data, isLoading, isError, isFetching } = useQuery({
    queryKey: ["products"],
    queryFn: () => reqQueryProducts()
})

// 4、组件上渲染
<Text>{data.data.username}</Text>

二、核心概念

1、useQuery

官方文档:useQuery | TanStack Query React Docs

queryKey

查询的唯一标识符,通常是一个数组。它被用于缓存、重新获取和在整个应用中共享数据。

queryFn

一个返回Promise的异步函数。你的实际数据获取逻辑(如:fetch、axios等)就放在这里。

enabled

当启用时,useQuery会进行查询,设置为false时,useQuery不会发其行动。如:在参数违规时可使用设置此属性为false来拒绝发送请求。

retry

错误重试的次数。

staleTime

数据的保鲜时间(有效时间),单位毫秒,当有效期过了后,再调用useQuery会重新请求数据并刷新缓存数据。在有效期内,再次获取数据会走缓存,不会重复请求数据。

gcTime

垃圾回收时间,单位毫秒,当用户未使用该数据且数据存在时间超过gcTime时,缓存数据会被直接清理掉,清理后下次访问需要重新获取数据。

返回结果

当我们调用了useQuery后,它会返回一个结果对象,对象结构如下:

参考文档:Queries | TanStack Query React Docs

|--------------|-----------------------------------------------------|
| | 描述 |
| data | 成功解析出的数据。 |
| isLoading | 仅在首次获取时为true(无缓存)。缓存里已有数据就不会loading了。 |
| isFetching | 在任何请求进行中(包括后台刷新)时为true。每次获取数据都会生效。 |
| isError | 如果查询遇到错误,则为true。 |
| error | 如果查询处于 isError 状态,则通过 error 属性可以获得该错误。 |
| isPending | 查询尚无数据 |
| status | 查询的状态的字符串,与其他几个判断状态的属性有类似效果 |
| refetch | 刷新useQuery查询结果的函数,可调用此函数来清理userQuery的查询缓存结果并重新获取数据。 |
| isRefetching | 第一次获取数据不会生效,后面重复发数据就会生效。第一次获取数据时无效。 |

注:isLoading只在状态中完全没有请求数据时才为true;isFetching在第一次加载和后续的静默刷新时都为true。

2、useMutation

官方文档:useMutation | TanStack Query React Docs

是 React Query 中用于处理 创建、更新、删除 等操作(非 GET 请求)的 Hook。

关键记住:定义函数 → 创建 mutation → 调用 mutate

目的:处理服务器端的副作用,如:POST、PUT、PATCH、DELETE。

mutationFn:执行数据变更的异步函数。

mutate:你调用来触发变更的函数。

onSuccess:变更成功后用于处理副作用的回调函数。

3、失效策略

失效并重新获取方式

官方文档:QueryClient | TanStack Query Docs

最常用和最安全的策略:失效并重新获取。

原理:使用queryClient.invalidateQueries将数据标记为"陈旧"。

优点:简单、安全,保持数据与服务器一致。

缺点:UI更新可能存在轻微的网络延迟。(用户可能看到短暂的loading页面)

乐观更新方式

为了极致 的用户体验,我们可以在服务器确认前立即更新 UI:乐观更新

原理:变更前手动更新缓存,若出错则回滚。

优点:最佳用户体验,感觉像是即时完成。

缺点:实现更复杂,需要小心处理错误和回滚逻辑。

场景:如点赞和收藏操作,先更新数据,若出现网络错误再进行自定义回滚操作,每次用户操作都会立即改变状态,以提高用户体验。

缓存

1、缓存查询

官方文档:useQuery | TanStack Query React Docs

javascript 复制代码
const [id, setId] = useState<string | undefined>()
const { data, isLoading } = useQuery({
    queryKey: ["products", id],
    queryFn: () => reqQueryProducts(id)
})

在此场景中,通过 useQuery 查询商品列表中的单条数据时:

  • 如果该 id 之前已被查询过,useQuery 将直接从缓存中获取已保存的数据,不会针对相同 id 发起重复请求;

  • 如果 id 发生变化,useQuery 会自动识别到变化,并获取新 id 对应的数据,保持数据与当前查询条件一致。

全局配置缓存

官方文档:QueryClient | TanStack Query Docs

javascript 复制代码
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 5, // 缓存保留时间(原 cacheTime)
      staleTime: 1000 * 60,  // 数据新鲜时间
    },
  },
});
各项配置详解
gcTime(原 cacheTime,v4.28.0+)
javascript 复制代码
gcTime: 1000 * 60 * 5 // 默认 5 分钟
  • 查询缓存过期后仍保留的时间(垃圾回收时间)

  • 在此期间内再次访问相同查询会从缓存快速返回,然后后台重新获取

  • 设为 0 表示查询结束后立即移除缓存

  • v4.28.0+cacheTime 改为 gcTime

  • v5+ :继续使用 gcTime

staleTime
javascript 复制代码
staleTime: 1000 * 60 * 10 // 默认 0 秒
  • 数据保持新鲜的时间

  • 在此时间内,相同查询不会重新获取

  • 设为 Infinity 表示数据永不过期

针对单个查询配置
javascript 复制代码
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  gcTime: 1000 * 60 * 10, // 10分钟
  staleTime: 1000 * 60 * 5, // 5分钟内数据是新鲜的
});
更精细的缓存控制

基于查询键的缓存时间

javascript 复制代码
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: (query) => {
        // 根据 queryKey 返回不同的 staleTime
        if (query.queryKey[0] === 'todos') {
          return 1000 * 60 * 5;
        }
        return 0;
      },
    },
  },
});
组合配置示例
javascript 复制代码
useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  // 10分钟内数据新鲜
  staleTime: 1000 * 60 * 10,
  // 缓存保留1小时(即使组件卸载)
  gcTime: 1000 * 60 * 60,
  // 自动刷新选项
  refetchOnWindowFocus: true,
  refetchOnReconnect: true,
  refetchOnMount: true,
});

2、缓存失效策略

主动失效缓存
javascript 复制代码
// 使特定查询失效
queryClient.invalidateQueries({ 
  queryKey: ['todos'] 
});

// 精确匹配
queryClient.invalidateQueries({ 
  queryKey: ['todo', 1], 
  exact: true 
});

3、设置缓存数据

官方文档:QueryClient | TanStack Query Docs

javascript 复制代码
// 更新缓存数据
queryClient.setQueryData(['todo', id], newTodo);

4、移除缓存数据

官方文档:QueryClient | TanStack Query Docs

javascript 复制代码
// 移除特定缓存
queryClient.removeQueries({ queryKey: ['todos'] });

// 垃圾回收(清除过期缓存)
queryClient.clear();

5、 实用配置建议

javascript 复制代码
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 实时数据(聊天、通知)
      gcTime: 1000 * 60 * 2,    // 2分钟
      staleTime: 0,             // 总是重新获取
      
      // 或静态数据(配置、字典)
      // gcTime: 1000 * 60 * 60, // 1小时
      // staleTime: Infinity,    // 永不过期
    },
  },
});

// 不同场景的最佳实践
const strategies = {
  realTime: {
    staleTime: 0,
    gcTime: 1000 * 60 * 2,
    refetchInterval: 1000 * 30, // 30秒轮询
  },
  sessionData: {
    staleTime: 1000 * 60 * 5,
    gcTime: 1000 * 60 * 30,
  },
  staticData: {
    staleTime: Infinity,
    gcTime: 1000 * 60 * 60 * 24, // 24小时
  },
};

根据你的应用场景选择合适的时间配置,实时数据建议较短的 staleTime,静态数据可以设置较长的缓存时间。

异常处理

核心要点总结:

  1. 必用属性error, isError, refetch

  2. 常用配置retry, retryDelay, onError

  3. 推荐实践:全局配置 + 具体查询覆盖

  4. 错误分类:网络错误、服务器错误、客户端错误

  5. 用户友好:显示清晰错误信息,提供重试按钮

错误重试案例

在模拟请求异常过程中,我们可以观察到,请求出错后 useQuery 自动进行了 3 次错误重试,并最终输出以下信息。这体现了 useQuery 对请求失败的有效异常处理机制。

基础用法

javascript 复制代码
import { useQuery } from '@tanstack/react-query';

const { 
  data, 
  error,        // 错误对象
  isError,      // 是否有错误(布尔值)
  isPending,    // 加载中(新版替代isLoading)
  refetch       // 重新获取函数
} = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  
  // 核心配置
  retry: 1,           // 失败时重试1次
  retryDelay: 1000,   // 重试延迟1秒
  
  // 错误回调
  onError: (error) => {
    console.error('查询失败:', error);
  }
});

简单错误显示

javascript 复制代码
function Todos() {
  const { data, error, isError, isPending } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  });

  if (isPending) return <div>加载中...</div>;
  
  if (isError) {
    return (
      <div>
        <p>错误: {error.message}</p>
        <button onClick={() => refetch()}>重试</button>
      </div>
    );
  }
  
  return <div>{/* 显示数据 */}</div>;
}

重试策略

javascript 复制代码
useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  
  // 条件重试
  retry: (failureCount, error) => {
    // 只在网络错误或服务器错误时重试
    if (error.status >= 500) return failureCount < 3; // 服务器错误重试3次
    if (error.message.includes('Network')) return failureCount < 2; // 网络错误重试2次
    return false; // 其他错误不重试
  },
  
  // 指数退避延迟
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});

全局配置(推荐)

javascript 复制代码
// src/app.js
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// 创建时配置全局错误处理
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2, // 所有查询默认重试2次
      onError: (error) => {
        // 全局错误处理(如发送到监控服务)
        console.error('全局查询错误:', error);
      },
    },
    mutations: {
      onError: (error) => {
        console.error('全局变更错误:', error);
      },
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* 你的应用 */}
    </QueryClientProvider>
  );
}

实用错误处理函数

javascript 复制代码
// 统一错误处理函数
function handleQueryError(error) {
  if (error.response?.status === 401) {
    // 未授权,跳转登录
    window.location.href = '/login';
  } else if (error.response?.status === 404) {
    // 资源不存在
    return { type: 'not-found', message: '请求的内容不存在' };
  } else if (error.message.includes('Network')) {
    // 网络错误
    return { type: 'network', message: '网络连接失败,请检查网络' };
  } else {
    // 其他错误
    return { type: 'error', message: error.message || '请求失败' };
  }
}

// 使用示例
const { data, error, isError } = useQuery({
  queryKey: ['data'],
  queryFn: fetchData,
  onError: handleQueryError,
});

错误抛出案例

javascript 复制代码
function DataComponent() {
  const { data, error, isError, isPending, refetch } = useQuery({
    queryKey: ['data'],
    queryFn: () => fetch('/api/data').then(res => {
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    }),
    retry: 1,
  });

  if (isPending) return <div>加载中...</div>;
  if (isError) return (
    <div>
      <p>错误: {error.message}</p>
      <button onClick={() => refetch()}>重试</button>
    </div>
  );
  
  return <div>{JSON.stringify(data)}</div>;
}

增删改副作用

1、基础使用

javascript 复制代码
import { useMutation } from '@tanstack/react-query';

// 1. 定义 API 函数
const addUser = async (newUser) => {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newUser),
  });
  return response.json();
};

// 2. 在组件中使用
function AddUserForm() {
  const mutation = useMutation({
    mutationFn: addUser,
    // 可选配置
    onSuccess: (data) => {
      console.log('添加成功:', data);
    },
    onError: (error) => {
      console.error('添加失败:', error);
    },
    onSettled: () => {
      console.log('请求完成(无论成功或失败)');
    },
  });

  const handleSubmit = (formData) => {
    mutation.mutate(formData);
  };

  return (
    <div>
      <button 
        onClick={() => handleSubmit({ name: 'John' })}
        disabled={mutation.isPending}
      >
        {mutation.isPending ? '添加中...' : '添加用户'}
      </button>
      
      {mutation.isError && (
        <div>错误: {mutation.error.message}</div>
      )}
      
      {mutation.isSuccess && (
        <div>用户添加成功!</div>
      )}
    </div>
  );
}

2、核心状态和函数

javascript 复制代码
const mutation = useMutation({
  mutationFn: yourApiFunction,
});

// 主要状态
mutation.status         // 'idle' | 'pending' | 'error' | 'success'
mutation.isIdle         // true | false
mutation.isPending      // true | false
mutation.isError        // true | false
mutation.isSuccess      // true | false

// 数据和错误
mutation.data           // 成功返回的数据
mutation.error          // 错误对象

// 主要方法
mutation.mutate(data)   // 触发 mutation
mutation.reset()        // 重置状态

// 异步版本
mutation.mutateAsync(data)  // 返回 Promise

3、实用示例

1. 表单提交
javascript 复制代码
function RegisterForm() {
  const [formData, setFormData] = useState({ email: '', password: '' });

  const registerMutation = useMutation({
    mutationFn: registerUser,
    onSuccess: () => {
      // 清空表单
      setFormData({ email: '', password: '' });
      alert('注册成功!');
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    registerMutation.mutate(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.email}
        onChange={(e) => setFormData({...formData, email: e.target.value})}
      />
      <button type="submit" disabled={registerMutation.isPending}>
        注册
      </button>
    </form>
  );
}
2. 带变量的更新操作
javascript 复制代码
const updateUser = useMutation({
  mutationFn: ({ userId, userData }) => 
    fetch(`/api/users/${userId}`, {
      method: 'PUT',
      body: JSON.stringify(userData),
    }).then(res => res.json()),
});

// 使用时传递对象
updateUser.mutate({ 
  userId: '123', 
  userData: { name: '新的名字' } 
});
3. 结合查询更新(自动重新获取)
javascript 复制代码
const queryClient = useQueryClient();

const addTodo = useMutation({
  mutationFn: postNewTodo,
  onSuccess: () => {
    // 使旧的缓存失效,触发重新获取
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

// 或者乐观更新
const addTodoOptimistic = useMutation({
  mutationFn: postNewTodo,
  onMutate: async (newTodo) => {
    // 取消正在进行的 todos 查询
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    
    // 保存当前状态的快照
    const previousTodos = queryClient.getQueryData(['todos']);
    
    // 乐观更新
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
    
    // 返回上下文,用于错误回滚
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // 出错时回滚
    queryClient.setQueryData(['todos'], context.previousTodos);
  },
  onSettled: () => {
    // 完成后重新获取确保数据一致
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

4、常用配置选项

javascript 复制代码
const mutation = useMutation({
  mutationFn: apiFunction,
  
  // 生命周期钩子
  onMutate: (variables) => {
    // 在 mutation 执行前调用,常用于乐观更新
  },
  onSuccess: (data, variables, context) => {
    // 成功时调用
  },
  onError: (error, variables, context) => {
    // 失败时调用
  },
  onSettled: (data, error, variables, context) => {
    // 完成时调用(无论成功失败)
  },
  
  // 重试配置
  retry: 3, // 失败时重试次数
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
});

结合Typescript语法

查询 Hook

javascript 复制代码
// hooks/useUsers.ts
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import { User } from '@/types/user';

// API 函数
const fetchUsers = async (): Promise<User[]> => {
  const response = await fetch('/api/users');
  if (!response.ok) throw new Error('Failed to fetch users');
  return response.json();
};

export const useUsers = (options?: UseQueryOptions<User[], Error>) => {
  return useQuery<User[], Error>({
    queryKey: ['users'], // 查询键
    queryFn: fetchUsers, // 查询函数
    ...options,
  });
};

// 带参数的查询
export const useUser = (id: number) => {
  return useQuery<User, Error>({
    queryKey: ['user', id], // 依赖 id 的查询键
    queryFn: async () => {
      const response = await fetch(`/api/users/${id}`);
      if (!response.ok) throw new Error('Failed to fetch user');
      return response.json();
    },
    enabled: !!id, // 只有 id 存在时才启用查询
  });
};

修改 Hook

javascript 复制代码
// hooks/useUserMutations.ts
import {
  useMutation,
  useQueryClient,
  UseMutationOptions,
} from '@tanstack/react-query';
import { User, CreateUserDto, UpdateUserDto } from '@/types/user';

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

  return useMutation<User, Error, CreateUserDto>({
    mutationFn: async (newUser) => {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newUser),
      });
      if (!response.ok) throw new Error('Failed to create user');
      return response.json();
    },
    onSuccess: () => {
      // 使 users 查询失效,触发重新获取
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
};

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

  return useMutation<User, Error, { id: number; data: UpdateUserDto }>({
    mutationFn: async ({ id, data }) => {
      const response = await fetch(`/api/users/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (!response.ok) throw new Error('Failed to update user');
      return response.json();
    },
    onSuccess: (data, variables) => {
      // 更新缓存
      queryClient.setQueryData(['user', variables.id], data);
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
};

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

  return useMutation<void, Error, number>({
    mutationFn: async (id) => {
      const response = await fetch(`/api/users/${id}`, {
        method: 'DELETE',
      });
      if (!response.ok) throw new Error('Failed to delete user');
    },
    onSuccess: (_, id) => {
      // 移除缓存
      queryClient.removeQueries({ queryKey: ['user', id] });
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
};

总结到此!

相关推荐
醉城夜风~6 分钟前
CSS元素显示模式(display)
前端·css
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅3 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学5 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
先吃饱再说5 小时前
编写一个颜色选择器之后,我理解了 React 工程化的三层架构
react.js·前端框架·前端工程化
先吃饱再说5 小时前
别说你懂 useEffect:从底层机制到生命周期管理,这篇全讲透了
react.js·前端框架
赵庆明老师5 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim