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'] });
    },
  });
};

总结到此!

相关推荐
鹏多多2 小时前
flutter-使用EventBus实现组件间数据通信
android·前端·flutter
UpgradeLink2 小时前
Electron项目使用electron-updater与UpgradeLink接入参考
开发语言·前端·javascript·笔记·electron·用户运营
程序员祥云2 小时前
技能特⻓回答
前端·面试
xiaoxue..3 小时前
React 新手村通关指南:状态、组件与魔法 UI
前端·javascript·react.js·ui
晚霞的不甘3 小时前
Flutter + OpenHarmony 架构演进:从单体到模块化、微前端与动态能力的现代化应用体系
前端·flutter·架构
代码or搬砖3 小时前
Vue生命周期总结(四个阶段,八个钩子函数)
前端·javascript·vue.js
梵尔纳多3 小时前
第一个 Electron 程序
前端·javascript·electron
鹏北海-RemHusband3 小时前
记录一次微前端改造:把 10+ 个独立 Vue 项目整合到一起
前端·javascript·vue.js
程序员小寒3 小时前
前端高频面试题之Promise相关方法
前端·javascript·面试