react笔记之tanstack

TanStack(以前叫 React Query 的作者团队)是一个专注于构建数据获取和状态管理工具 的开源组织,其最著名的项目是 TanStack Query (以前叫 React Query)。虽然名字里有 "React",但 TanStack 现在已发展为一个框架无关的工具集,支持 React、Vue、Svelte、Solid 等多种前端框架。

核心作用:解决数据获取(Data Fetching)问题

在传统 React 应用中,开发者常常需要手动处理:

  • 发起 API 请求
  • 缓存数据
  • 处理加载/错误状态
  • 自动重新获取(如窗口聚焦时)
  • 分页、无限滚动、预取等高级功能

TanStack Query(React Query) 通过声明式的方式自动处理这些复杂逻辑,让你更专注于 UI 和业务逻辑。


TanStack 主要项目包括:

项目 用途
TanStack Query 数据获取、缓存、同步、后台更新等(核心产品)
TanStack Router 类型安全、基于文件系统的路由(适用于 React)
TanStack Table 高性能、可定制的表格组件(支持虚拟滚动、排序、过滤等)
TanStack Form 表单状态管理(仍在开发中,目标是类型安全、高性能)
TanStack Virtual 虚拟滚动(用于长列表优化性能)

所有这些库都强调:类型安全(TypeScript 优先)、高性能、零依赖、框架无关(或适配多框架)

TanStack Query: keepPreviousData

keepPreviousData@tanstack/react-query(旧称 React Query)中 useQuery 的一个可选配置选项,它的作用是:

当查询键(queryKey)发生变化、触发新请求时,在新数据加载完成前,继续保留并显示上一次的数据(而不是立即变成 undefined 或重置为初始状态)。


🎯 解决什么问题?

在分页、筛选、搜索等场景中,用户切换参数(比如从第 1 页切到第 2 页),queryKey 会变化,从而触发新的 useQuery 请求。

默认行为:

  • 一旦 queryKey 改变,当前 data 会被立即清除(变为 undefined
  • UI 会短暂显示"加载中"或空白,即使上一页的数据仍然有效

开启 keepPreviousData: true 后:

  • 即使 queryKey 变了,上一次的 data 仍会保留
  • 直到新数据加载完成,才替换为新数据
  • 用户体验更流畅,避免闪烁或空白

✅ 使用示例:分页场景

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

function UserList({ page }) {
  const { data, isLoading, isFetching } = useQuery({
    queryKey: ['users', page],
    queryFn: () => fetch(`/api/users?page=${page}`).then(res => res.json()),
    keepPreviousData: true, // 👈 关键配置
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      {/* 即使 page 变化,data 也不会立即消失 */}
      {data?.users.map(user => <div key={user.id}>{user.name}</div>)}
      {isFetching && <div>加载下一页...</div>} {/* 注意:用 isFetching 而不是 isLoading */}
    </div>
  );
}

💡 注意:在 keepPreviousData: true 时,应使用 isFetching 来判断是否正在加载新数据,因为 isLoading 在有缓存数据时可能为 false


⚠️ 注意事项

  1. 不会阻止新请求:它只是保留旧数据,新请求照常发起。

  2. 不影响缓存:TanStack Query 依然会按正常逻辑缓存新旧数据。

  3. 适用于"相似数据结构"的场景:比如分页、搜索、标签切换等。如果新旧数据结构完全不同,保留旧数据可能导致 UI 错误。

  4. v4+ 中已弃用?

    实际上,在 TanStack Query v4 及以上版本中,keepPreviousData 仍然是有效选项 ,但官方推荐优先使用 placeholderDatainitialData 来实现类似效果(更灵活)。不过 keepPreviousData 依然广泛使用且支持。

    补充:从 v5 开始,keepPreviousData 已被移除

    如果你使用的是 v5+,应该改用:

    复制代码
    placeholderData: keepPreviousData()

    其中 keepPreviousData 是一个 helper 函数(需从 @tanstack/react-query 导入)。

    ✅ v5+ 正确写法

    javascript 复制代码
    import { keepPreviousData, useQuery } from '@tanstack/react-query';
    
    useQuery({
      queryKey: ['users', page],
      queryFn: fetchUsers,
      placeholderData: keepPreviousData, // 👈 v5+ 方式
    });

总结

版本 用法
v3 / v4 keepPreviousData: true
v5+ placeholderData: keepPreviousData(作为函数传入)

核心目的 :在参数变化导致查询刷新时,保持 UI 稳定,避免数据闪空,提升用户体验。


TanStack Query: enabled

@tanstack/react-queryuseQuery 中,enabled: Boolean 是一个条件控制选项 ,用于决定该查询 是否自动执行


✅ 核心作用

  • enabledtrue(或真值)时:自动发起请求(即"挂载时自动 fetch")。
  • enabledfalse(或假值)时:不自动发起请求,即使组件已挂载。

这常用于 按条件启用/禁用查询 的场景。


📌 例子解释

javascript 复制代码
useQuery({
  queryKey: ['enterpriseData'],
  queryFn: fetchEnterpriseData,
  enabled: IS_ENTERPRISE, // ← 假设 IS_ENTERPRISE 是一个布尔值
});
  • 如果 IS_ENTERPRISE === true

    → 组件一渲染,就自动调用 fetchEnterpriseData() 获取企业数据。

  • 如果 IS_ENTERPRISE === false

    完全不会发起网络请求data 保持为 undefinedisLoadingfalse

    → 此时你可以安全地忽略这个查询,比如普通用户不需要加载企业专属数据。


🔧 典型使用场景

  1. 权限控制

    只有企业用户才加载某些敏感或高级数据:

    复制代码
    enabled: user?.role === 'enterprise'
  2. 依赖前置数据

    必须等某个 ID 有值才发起请求:

    复制代码
    enabled: !!userId
  3. 懒加载 / 手动触发

    结合 refetch() 实现点击按钮再加载

    复制代码
    const { refetch } = useQuery({ ..., enabled: false });
    
    const handleClick = () => refetch();
  4. 环境判断

    仅在生产环境或特定环境下启用

    复制代码
    enabled: process.env.NODE_ENV === 'production'

⚠️ 注意事项

  • 即使 enabled: false,你仍然可以通过 refetch() 手动触发请求
  • enabled 变为 true 后,会立即自动执行查询(如果数据不在缓存中或已过期)。
  • 不要和 staleTimecacheTime 混淆 ------ enabled 控制的是 是否发起请求,而不是缓存行为。

💡 小技巧:组合使用

复制代码
const { data, isLoading, refetch } = useQuery({
  queryKey: ['report', reportId],
  queryFn: () => api.getReport(reportId),
  enabled: !!reportId && IS_ENTERPRISE, // 两个条件都满足才加载
});

这样可以避免因 reportId 为空或用户无权限而发出无效请求。


总结

enabled: IS_ENTERPRISE 的意思是:只有当 IS_ENTERPRISE 为真时,这个查询才会自动运行;否则,它会被"禁用",不发请求、不报错、安静等待。

这是 TanStack Query 中实现条件化数据获取的关键机制,能有效减少不必要的网络请求,提升性能和安全性。


TanStack Query: retry

@tanstack/react-queryuseQuery 中,retry: false 表示:

当查询函数(queryFn)抛出错误时,不进行任何重试,立即标记该查询为"失败"状态。


📌 默认行为 vs 设置 retry: false

  • 默认情况下 (未配置 retry):

    • TanStack Query 会在查询失败后 自动重试 3 次(指数退避延迟:~1s、2s、4s...)。
    • 这对临时性网络问题或服务抖动很有用。
  • 设置 retry: false

    • 一旦请求失败(比如 API 返回 500 或网络断开),立刻停止,不再重试。
    • 错误会立即暴露给 UI(通过 errorisError)。

✅ 使用示例

javascript 复制代码
const { data, error, isError, isLoading } = useQuery({
  queryKey: ['userData'],
  queryFn: fetchUserData,
  retry: false, // ← 失败就失败,别重试!
});

如果 fetchUserData() 抛出异常:

  • 不会再尝试第 2、3、4 次
  • isError 立即变为 true
  • error 包含第一次失败的错误对象

🔧 retry 的其他取值

行为
false 完全不重试(推荐用于用户操作类请求,如提交表单)
true3(数字) 最多重试 3 次(默认是 3
(failureCount, error) => boolean 自定义重试逻辑(例如只对 5xx 重试)
自定义重试示例(仅对服务器错误重试):
javascript 复制代码
retry: (failureCount, error) => {
  // 只有 HTTP 5xx 错误才重试,最多 2 次
  return error.statusCode >= 500 && failureCount < 2;
}

🎯 什么时候用 retry: false

  1. 用户主动触发的操作

    比如点击"获取验证码",失败了应该立刻提示用户,而不是默默重试。

  2. 已知不可恢复的错误

    如 401(未授权)、403(禁止访问)、404(资源不存在)------这些错误重试也没用。

  3. 避免干扰用户体验

    如果错误需要用户干预(如输入正确信息),自动重试反而会造成混乱。

  4. 调试阶段

    快速看到首次错误,便于排查问题。


⚠️ 注意

  • retry 只对抛出异常(reject)的 queryFn 生效 。如果你在 queryFncatch 了错误并返回了正常值,Query 会认为"成功",不会触发重试逻辑。
  • 即使 retry: false,你仍然可以通过 UI 按钮调用 refetch() 手动重试

TanStack Query: useQueryClient

useQueryClient@tanstack/react-query 提供的一个 React Hook,它的作用是:

让你在组件中获取当前应用的 QueryClient 实例,从而可以手动操作 React Query 的缓存、触发 refetch、预取数据、清除缓存等高级操作。


🧠 为什么需要它?

React Query 默认自动管理数据获取和缓存,但在很多实际场景中,你可能需要主动干预缓存或查询行为,比如:

  • 用户提交表单后,手动刷新某个列表(如"新增用户后刷新用户列表")
  • 乐观更新(Optimistic Update):先更新 UI,再同步到服务器,失败则回滚
  • 预取数据(Prefetching):在用户可能访问某页前预先加载数据
  • 使某些查询失效(invalidateQueries):让缓存过期,下次自动重新请求
  • 直接设置/修改缓存数据(setQueryData)

这些操作都需要通过 QueryClient 实例完成,而 useQueryClient() 就是在函数组件中获取这个实例的标准方式。


✅ 基本用法

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

function UserProfile({ userId }) {
  const queryClient = useQueryClient();

  const handleUpdateUser = async (newData) => {
    // 1. 乐观更新:先更新缓存中的数据
    queryClient.setQueryData(['user', userId], (oldData) => ({
      ...oldData,
      ...newData,
    }));

    try {
      // 2. 发送请求到服务器
      await updateUser(userId, newData);
    } catch (error) {
      // 3. 失败时回滚缓存
      queryClient.invalidateQueries({ queryKey: ['user', userId] });
    }
  };

  return <button onClick={() => handleUpdateUser({ name: 'New Name' })}>更新</button>;
}

🔧 常用方法(通过 queryClient 调用)

方法 用途
setQueryData(queryKey, updater) 直接写入缓存(用于乐观更新、本地修改)
getQueryData(queryKey) 读取当前缓存中的数据(不触发请求)
invalidateQueries({ queryKey: [...] }) 标记查询为无效,下次使用时自动重新 fetch
refetchQueries({ queryKey: [...] }) 立即重新请求匹配的查询
prefetchQuery({ queryKey, queryFn }) 提前加载数据到缓存(常用于 hover 或滚动预加载)
removeQueries({ queryKey: [...] }) 从缓存中移除特定查询
cancelQueries(...) 取消正在进行的查询

🌰 实际场景示例:提交后刷新列表

javascript 复制代码
function CreateUserForm() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (userData) => createUser(userData),
    onSuccess: () => {
      // 成功后让 users 列表缓存失效,自动重新加载
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      mutation.mutate({ name: 'Alice' });
    }}>
      <button type="submit">创建用户</button>
    </form>
  );
}

⚠️ 注意事项

  • useQueryClient() 必须在 <QueryClientProvider> 的子组件中使用。
  • 不要滥用 setQueryData,确保数据结构与原始查询一致。
  • 在 v4/v5 中 API 略有变化(如 invalidateQueries 的参数格式),建议查阅对应版本文档。

总结

useQueryClient() 是你与 React Query 缓存系统交互的"控制台"

它赋予你在自动数据管理之外的手动控制能力,是实现高级交互(如乐观更新、缓存同步、预加载)的关键工具。

官方文档参考:https://tanstack.com/query/latest/docs/react/reference/useQueryClient

TanStack Query: useMutation

useMutation@tanstack/react-query(TanStack Query)提供的一个 React Hook ,专门用于处理需要改变服务器状态的操作,比如:

  • 创建数据(POST)
  • 更新数据(PUT / PATCH)
  • 删除数据(DELETE)
  • 登录、上传文件、发送消息等具有副作用的异步操作

🎯 核心作用

封装并简化"写操作"(mutations)的流程,自动处理加载状态、错误、成功回调、缓存更新等常见逻辑。

useQuery(用于"读")不同,useMutation 用于"写",它不会自动执行 ,而是返回一个函数(通常叫 mutatemutateAsync),由你手动触发。


✅ 基本用法

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

function CreateUserForm() {
  const { mutate, isLoading, isError, error, isSuccess } = useMutation({
    mutationFn: (newUser) => 
      fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(newUser),
      }).then(res => res.json()),
    
    // 可选:成功后的回调
    onSuccess: (data) => {
      console.log('用户创建成功:', data);
      // 通常在这里让相关查询失效或直接更新缓存
    },
    
    // 可选:失败后的回调
    onError: (error) => {
      console.error('创建失败:', error);
    }
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    const newUser = { name: 'Alice' };
    mutate(newUser); // 👈 手动触发 mutation
  };

  if (isLoading) return <div>提交中...</div>;
  if (isError) return <div>错误:{error.message}</div>;
  if (isSuccess) return <div>✅ 创建成功!</div>;

  return <form onSubmit={handleSubmit}>...</form>;
}

🔑 关键特性

特性 说明
手动触发 必须调用 mutate(variables) 才会执行
状态管理 自动提供 isLoadingisErrorisSuccessdataerror 等状态
重试机制 支持 retry 配置(默认为 0,即不重试,因为写操作通常不可重试)
缓存联动 成功后可通过 onSuccess 调用 queryClient.invalidateQueries() 刷新相关查询,或使用 setQueryData/setQueriesData 直接更新缓存(乐观更新)
取消/清理 支持在 mutation 函数中处理取消信号(如 AbortController)

🔄 与缓存交互(最佳实践)

Mutation 通常会影响已有的查询数据,推荐在 onSuccess使相关查询失效,让 TanStack Query 自动重新拉取最新数据:

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

const queryClient = useQueryClient();

useMutation({
  mutationFn: updateUser,
  onSuccess: () => {
    // 使所有以 ['users'] 开头的查询失效
    queryClient.invalidateQueries({ queryKey: ['users'] });
    
    // 或精确失效某个用户
    // queryClient.invalidateQueries({ queryKey: ['user', userId] });
  }
});

💡 更高级的做法:乐观更新(Optimistic Update)

在请求发出前就更新 UI,失败再回滚,体验更流畅。

目标 :用户点击"添加待办事项"后,立刻在 UI 上显示新任务 (即使网络请求还没完成),如果请求失败,自动回滚,删除刚才添加的假数据。


⚠️ 注意事项

  1. 不要用于"读"操作useMutation 是为"写"设计的,不要用它来获取数据。

  2. 默认不重试:因为写操作(如创建订单)重复执行可能造成副作用。

  3. 变量传递mutate(variables) 中的 variables 会作为唯一参数传给 mutationFn

  4. 异步版本mutateAsync 返回 Promise,适合需要 await 的场景:

    javascript 复制代码
    try {
      const data = await mutateAsync(newUser);
    } catch (error) {
      // 处理错误
    }

总结

useMutation 是 TanStack Query 中处理"写操作"的标准方式,它帮你管理异步状态、错误处理,并无缝集成缓存更新机制,让你专注业务逻辑而非样板代码。

适用于:

  • 表单提交
  • 删除按钮
  • 点赞/收藏
  • 文件上传
  • 用户登录/注册等任何改变服务器状态的操作

官方文档:https://tanstack.com/query/latest/docs/react/guides/mutations


**TanStack Table:**useReactTable

useReactTable@tanstack/react-table (TanStack Table v8+)中的核心 Hook,用于在 React 应用中创建和管理一个功能强大、高度可定制的表格实例


🎯 核心作用

useReactTable 接收表格的配置(如数据、列定义、分页、排序、筛选等),返回一个表格实例对象,该对象包含:

  • 表格的状态(当前页、排序字段、筛选值等)
  • 表格的元数据(行、列、单元格结构)
  • 用于渲染和交互的方法(如 getRowModel(), getHeaderGroups() 等)

你用它来驱动 UI 渲染,而 TanStack Table 负责处理所有复杂的表格逻辑。

💡 它本身不渲染任何 DOM,只提供数据和逻辑 ------ 你需要自己用 JSX 渲染表格结构(完全控制样式和行为)。


✅ 基本使用示例

javascript 复制代码
import { useReactTable, getCoreRowModel } from '@tanstack/react-table';
import { useMemo } from 'react';

function MyTable({ data, columns }) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(), // 必需:获取基础行模型
  });

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map(headerGroup => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map(header => (
              <th key={header.id}>
                {header.isPlaceholder ? null : header.renderHeader()}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map(row => (
          <tr key={row.id}>
            {row.getVisibleCells().map(cell => (
              <td key={cell.id}>{cell.renderCell()}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

🔧 支持的高级功能(通过插件/选项启用)

useReactTable 的强大之处在于它的插件化架构。你可以按需启用功能:

功能 配置方式
分页 getPaginationRowModel: getPaginationRowModel()
排序 getSortedRowModel: getSortedRowModel() + 列定义 enableSorting: true
筛选 getFilteredRowModel: getFilteredRowModel() + 列定义 filterFn
行选择 enableRowSelection: true + getIsSelected() 等方法
列可见性 enableHiding: true
列尺寸调整 需配合 flexRender 和 CSS 或第三方库
虚拟滚动 需结合 @tanstack/virtual

例如,启用分页和排序:

javascript 复制代码
const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getSortedRowModel: getSortedRowModel(),
});

然后你可以调用:

  • table.nextPage()
  • table.previousPage()
  • table.setSorting([{ id: 'name', desc: false }])

📦 关键特点

  1. 框架无关核心 :TanStack Table 的核心是框架无关的,useReactTable 是其 React 封装。
  2. 零样式:不带任何默认样式,完全由你控制外观(适合 Tailwind、CSS-in-JS、Ant Design 等)。
  3. 高性能:支持大数据集(配合虚拟滚动)、细粒度更新。
  4. 类型安全:对 TypeScript 支持极佳,列定义、数据类型自动推导。
  5. 组合式 API:按需引入功能,避免打包体积膨胀。

🆚 与旧版(React Table v7)的区别

  • v7 使用 useTable() + 大量 HOC 插件(如 useSortBy, usePagination
  • v8+(TanStack Table)改用 useReactTable() + 函数式插件(如 getSortedRowModel),更灵活、更函数式、更好 TypeScript 支持

官方推荐使用场景

  • 数据仪表盘
  • 后台管理系统
  • 需要复杂交互的表格(多级表头、拖拽、编辑、展开行等)
  • 对性能或定制性要求高的项目

总结

useReactTable 是 TanStack Table 在 React 中的入口 Hook,它将你的数据和列配置转换为一个功能完整的表格逻辑引擎,让你自由地构建高性能、可交互的表格 UI。

官网文档:https://tanstack.com/table/latest/docs/guide/introduction

GitHub 示例:https://github.com/TanStack/table/tree/main/examples/react

如果你需要分页、排序、筛选、选择等企业级表格功能,useReactTable 是目前 React 生态中最强大且灵活的选择之一。


举个 React + TanStack Query 的简单例子:

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

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>Hello, {data.name}!</div>;
}

TanStack Query 会自动:

  • 缓存 user 数据(相同 queryKey 不会重复请求)
  • 在组件重新挂载时返回缓存数据(瞬时显示)
  • 后台自动刷新(可配置)
  • 处理竞态条件(race conditions)

总结

TanStack 是一套现代化前端工具库集合,核心目标是:

让数据获取、路由、表格、表单等常见任务变得更简单、更高效、更类型安全。

如果你在用 React 做数据密集型应用(如后台管理系统、仪表盘等),TanStack Query 几乎是行业标准,强烈推荐使用。

相关推荐
2501_921930834 小时前
基础入门 React Native 鸿蒙跨平台开发:react-native-button三方库适配
react native·react.js·harmonyos
学嵌入式的小杨同学10 小时前
从零打造 Linux 终端 MP3 播放器!用 C 语言实现音乐自由
linux·c语言·开发语言·前端·vscode·ci/cd·vim
weixin_4255437311 小时前
TRAE CN3.3.25 构建的Electron简易DEMO应用
前端·typescript·electron·vite·nestjs
Mr Xu_11 小时前
【Vue3 + ECharts 实战】正确使用 showLoading、resize 与 dispose 避免内存泄漏
前端·信息可视化·vue·echarts
0思必得012 小时前
[Web自动化] Selenium设置相关执行文件路径
前端·爬虫·python·selenium·自动化
雯0609~12 小时前
hiprint:实现项目部署与打印1-官网提供普通html版本
前端·html
不绝19112 小时前
UGUI——进阶篇
前端
童话名剑13 小时前
序列模型与集束搜索(吴恩达深度学习笔记)
人工智能·笔记·深度学习·机器翻译·seq2seq·集束搜索·编码-解码模型
Exquisite.13 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql