TanStack功能介绍和使用场景,对应 vue,react 完整使用示例

TanStack 功能介绍与使用场景

TanStack 是一个专注于构建无头(Headless)类型安全框架无关 的工具库集合。它的前身是著名的 React Query 团队,现已发展成为一个支持 React, Vue, Solid, Svelte, Angular 等多前端框架的生态系统。

其核心理念是: "学会一次,到处复用" 。你只需要掌握一套 API 逻辑,就可以在任何支持的框架中使用,极大地降低了跨框架开发的学习成本和迁移成本。

核心功能模块与使用场景

模块 原名 核心功能 典型使用场景
TanStack Query React Query 异步状态管理。自动处理数据获取、缓存、后台同步、加载/错误状态、重试机制、窗口聚焦重新获取等。 替代手写的 axios + useState + useEffect 组合;管理复杂的服务器状态;需要离线缓存或乐观更新的场景。
TanStack Table React Table 无头表格逻辑。提供排序、过滤、分页、分组、虚拟滚动等核心逻辑,不渲染任何 HTML,完全由你控制 UI。 构建高度定制化的企业级数据表格;需要复杂交互(如多列排序、服务端分页)的后台管理系统。
TanStack Router - 类型安全的路由。利用文件系统路由和强大的类型推导,实现参数、搜索查询的端到端类型安全。 对类型安全要求极高的中大型应用;需要预加载数据或权限控制的复杂路由场景。
TanStack Virtual React Virtual 列表虚拟化。高效渲染超长列表或网格,只渲染可视区域内的 DOM 元素。 聊天历史记录、无限滚动的信息流、大型数据表格的行虚拟化。
TanStack Form - 表单管理。提供高性能的表单状态管理,支持复杂的验证逻辑(常与 Zod 结合)。 复杂的动态表单、向导式表单、需要高性能验证的大型表单。
TanStack Start - 全栈元框架。基于 Vite 和 TanStack Router,提供 SSR、流式传输、服务器函数等能力。 构建类似 Next.js/Nuxt 的全栈应用,但希望使用 TanStack 生态的统一体验。

完整使用示例:Vue 3 vs React

以下将以最常用的 TanStack Query (数据获取) 和 TanStack Table (表格展示) 为例,对比 Vue 3 和 React 的完整用法。你会发现逻辑几乎一模一样,只是语法糖不同。

1. TanStack Query: 数据获取与缓存

场景:从 API 获取用户列表,并支持删除用户(突变)。

🅰️ Vue 3 示例 (@tanstack/vue-query)

安装:

bash 复制代码
npm install @tanstack/vue-query

1. 初始化 ( main.ts):

typescript 复制代码
import { createApp } from 'vue'
import { VueQueryPlugin } from '@tanstack/vue-query'
import App from './App.vue'

const app = createApp(App)
app.use(VueQueryPlugin) // 注册插件
app.mount('#app')

2. 使用组件 ( UserList.vue):

vue 复制代码
<script setup lang="ts">
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
import axios from 'axios'

// --- 1. 查询数据 (GET) ---
const fetchUsers = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/users')
  return data
}

const { data, isLoading, error, refetch } = useQuery({
  queryKey: ['users'], // 唯一的缓存键
  queryFn: fetchUsers,
  staleTime: 1000 * 60 * 5, // 5分钟内数据视为新鲜,不重新请求
})

// --- 2. 修改数据 (DELETE) ---
const queryClient = useQueryClient()

const deleteMutation = useMutation({
  mutationFn: (id: number) => axios.delete(`https://jsonplaceholder.typicode.com/users/${id}`),
  onSuccess: () => {
    // 成功后使 'users' 查询失效,触发自动重新获取
    queryClient.invalidateQueries({ queryKey: ['users'] })
  },
})

const handleDelete = (id: number) => {
  if(confirm('确定删除?')) {
    deleteMutation.mutate(id)
  }
}
</script>

<template>
  <div>
    <h2>用户列表 (Vue 3)</h2>
    
    <!-- 加载状态 -->
    <div v-if="isLoading">加载中...</div>
    
    <!-- 错误状态 -->
    <div v-else-if="error">错误: {{ (error as Error).message }}</div>
    
    <!-- 数据列表 -->
    <ul v-else>
      <li v-for="user in data" :key="user.id">
        {{ user.name }} 
        <button @click="handleDelete(user.id)" :disabled="deleteMutation.isPending.value">
          {{ deleteMutation.isPending.value ? '删除中...' : '删除' }}
        </button>
      </li>
    </ul>

    <button @click="refetch" style="margin-top: 20px;">手动刷新</button>
  </div>
</template>
⚛️ React 示例 (@tanstack/react-query)

安装:

bash 复制代码
npm install @tanstack/react-query

1. 初始化 ( main.tsx):

tsx 复制代码
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'

const queryClient = new QueryClient() // 创建客户端实例

ReactDOM.createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}> {/* 包裹应用 */}
    <App />
  </QueryClientProvider>,
)

2. 使用组件 ( UserList.tsx):

tsx 复制代码
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'

export default function UserList() {
  const queryClient = useQueryClient()

  // --- 1. 查询数据 (GET) ---
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['users'],
    queryFn: async () => {
      const { data } = await axios.get('https://jsonplaceholder.typicode.com/users')
      return data
    },
    staleTime: 1000 * 60 * 5,
  })

  // --- 2. 修改数据 (DELETE) ---
  const deleteMutation = useMutation({
    mutationFn: (id: number) => axios.delete(`https://jsonplaceholder.typicode.com/users/${id}`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] })
    },
  })

  if (isLoading) return <div>加载中...</div>
  if (error) return <div>错误: {(error as Error).message}</div>

  return (
    <div>
      <h2>用户列表 (React)</h2>
      <ul>
        {data?.map((user: any) => (
          <li key={user.id}>
            {user.name}{' '}
            <button 
              onClick={() => handleDelete(user.id)} 
              disabled={deleteMutation.isPending}
            >
              {deleteMutation.isPending ? '删除中...' : '删除'}
            </button>
          </li>
        ))}
      </ul>
      <button onClick={() => refetch()} style={{ marginTop: '20px' }}>手动刷新</button>
    </div>
  )

  function handleDelete(id: number) {
    if (confirm('确定删除?')) {
      deleteMutation.mutate(id)
    }
  }
}

对比总结 : 逻辑配置(queryKey, queryFn, invalidateQueries)完全一致。Vue 中使用 .value 访问响应式对象(如 isPending.value),而 React 直接访问属性。


2. TanStack Table: 高级表格

场景 :展示用户数据,支持排序分页 。TanStack Table 是 "Headless" 的,意味着它只提供逻辑(行、列、页码计算),你需要自己写 <table> 标签。

🅰️ Vue 3 示例 (@tanstack/vue-table)

安装:

bash 复制代码
npm install @tanstack/vue-table

组件代码 ( UserTable.vue):

vue 复制代码
<script setup lang="ts">
import { ref, computed } from 'vue'
import {
  FlexRender,
  getCoreRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useVueTable,
} from '@tanstack/vue-table'

// 模拟数据
const data = ref([
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 35 },
  { id: 4, name: 'David', age: 20 },
])

// 定义列
const columns = [
  {
    header: '姓名',
    accessorKey: 'name',
    cell: ({ getValue }: any) => getValue<string>(),
  },
  {
    header: '年龄',
    accessorKey: 'age',
    cell: ({ getValue }: any) => getValue<number>(),
  },
]

// 初始化表格
const table = useVueTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getSortedRowModel: getSortedRowModel(),
  state: {
    // 可以在此绑定响应式状态
  },
})
</script>

<template>
  <div>
    <table border="1" cellpadding="8">
      <thead>
        <tr v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
          <th 
            v-for="header in headerGroup.headers" 
            :key="header.id"
            @click="header.column.toggleSorting()"
            style="cursor: pointer; user-select: none;"
          >
            <FlexRender :render="header.column.columnDef.header" :props="header.getContext()" />
            {{ header.column.getIsSorted() ? (header.column.getIsSorted() === 'asc' ? ' 🔼' : ' 🔽') : '' }}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in table.getRowModel().rows" :key="row.id">
          <td v-for="cell in row.getVisibleCells()" :key="cell.id">
            <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
          </td>
        </tr>
      </tbody>
    </table>

    <!-- 分页控件 -->
    <div style="margin-top: 10px;">
      <button 
        @click="table.setPageIndex(0)" 
        :disabled="!table.getCanPreviousPage()"
      >
        首页
      </button>
      <button 
        @click="table.previousPage()" 
        :disabled="!table.getCanPreviousPage()"
      >
        上一页
      </button>
      <span> 第 {{ table.getState().pagination.pageIndex + 1 }} 页 </span>
      <button 
        @click="table.nextPage()" 
        :disabled="!table.getCanNextPage()"
      >
        下一页
      </button>
      <button 
        @click="table.setPageIndex(table.getPageCount() - 1)" 
        :disabled="!table.getCanNextPage()"
      >
        末页
      </button>
    </div>
  </div>
</template>
⚛️ React 示例 (@tanstack/react-table)

安装:

bash 复制代码
npm install @tanstack/react-table

组件代码 ( UserTable.tsx):

tsx 复制代码
import { useState } from 'react'
import {
  flexRender,
  getCoreRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
  ColumnDef,
} from '@tanstack/react-table'

type Person = { id: number; name: string; age: number }

const data: Person[] = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 35 },
  { id: 4, name: 'David', age: 20 },
]

const columns: ColumnDef<Person>[] = [
  {
    header: '姓名',
    accessorKey: 'name',
    cell: ({ getValue }) => getValue<string>(),
  },
  {
    header: '年龄',
    accessorKey: 'age',
    cell: ({ getValue }) => getValue<number>(),
  },
]

export default function UserTable() {
  const [sorting, setSorting] = useState([])
  const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 2 })

  const table = useReactTable({
    data,
    columns,
    state: { sorting, pagination },
    onSortingChange: setSorting,
    onPaginationChange: setPagination,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
  })

  return (
    <div>
      <table border="1" cellPadding="8">
        <thead>
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => (
                <th
                  key={header.id}
                  onClick={header.column.getToggleSortingHandler()}
                  style={{ cursor: 'pointer', userSelect: 'none' }}
                >
                  {flexRender(header.column.columnDef.header, header.getContext())}
                  {{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? null}
                </th>
              ))}
            </tr>
          ))}
        </thead>
        <tbody>
          {table.getRowModel().rows.map((row) => (
            <tr key={row.id}>
              {row.getVisibleCells().map((cell) => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>

      {/* 分页控件 */}
      <div style={{ marginTop: '10px' }}>
        <button onClick={() => table.setPageIndex(0)} disabled={!table.getCanPreviousPage()}>
          首页
        </button>
        <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
          上一页
        </button>
        <span> 第 {table.getState().pagination.pageIndex + 1} 页 </span>
        <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
          下一页
        </button>
        <button
          onClick={() => table.setPageIndex(table.getPageCount() - 1)}
          disabled={!table.getCanNextPage()}
        >
          末页
        </button>
      </div>
    </div>
  )
}

对比总结:

总结

TanStack 的最大优势在于统一性 。一旦你理解了 queryKey 的概念或 useTable 的配置项,你就可以在 React、Vue 甚至 Solid 项目中无缝切换,无需重新学习新的 API。对于现代前端工程化而言,它是提升开发效率和代码质量的神器。

相关推荐
新晨4372 小时前
Git跨分支文件恢复:如何将其他分支的内容安全拷贝到当前分支
前端·git
一枚菜鸟_2 小时前
02-React+TypeScript基础速览
前端·taro
踩着两条虫2 小时前
VTJ.PRO 在线应用开发平台入门与项目初始化
前端·人工智能·ai编程
码路人2 小时前
VUE-组件命名与注册机制
vue.js
流星雨在线2 小时前
大前端通用性能优化(高频场景专项)
前端·性能优化
方安乐2 小时前
ESLint代码规范(一)
前端·javascript·代码规范
酉鬼女又兒2 小时前
零基础快速入门前端JavaScript Array 常用方法详解与实战(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·chrome·蓝桥杯
January12072 小时前
Vue3打卡计时器:完整实现与优化方案
前端·javascript·css
GISer_Jing2 小时前
React全解析:从入门到精通实战指南
前端·react.js·前端框架