mcpField
T
tanstack

tanstack

github.com/tenequm开发3989 次安装2 天前更新
代码仓库

关于 tanstack

tanstack 汇集面向 React 应用的 Query、Router 和 Start:分别处理服务端状态、路由及全栈能力,适合构建从 SPA 到 SSR 应用的开发者。其重点是类型安全的数据操作、导航与服务端函数。

主要功能

  1. 01服务端状态获取、缓存与变更
  2. 02文件式路由与类型安全导航
  3. 03校验搜索参数并加载路由数据
  4. 04SSR、服务端函数与中间件
  5. 05支持流式查询与 AI/SSE 响应

适用场景

  1. 01为 React 应用缓存 API 数据
  2. 02构建带参数校验的文件式路由
  3. 03开发含 SSR 和服务端函数的全栈应用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tenequm/skills --skill tanstack

安装后在 Claude Code 中输入/tanstack确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CHANGELOG.md
LICENSE.txt
references/
code-splitting.md
data-loading.md
infinite-queries.md
middleware.md
optimistic-updates.md
query-guide.md
query-invalidation.md
query-performance.md
query-typescript.md
router-guide.md
router-ssr.md
routing-patterns.md
search-params.md
server-functions.md
server-routes.md
ssr-modes.md
start-guide.md

详细介绍

TanStack(Query + Router + Start)

用于 React 应用的类型安全库。Query 管理服务端状态(获取、缓存、变更)。Router 提供基于文件的路由,以及经过验证的搜索参数和数据加载器。Start 通过 SSR、服务端函数和中间件扩展 Router,支持构建全栈应用。

适用场景

Query - 数据获取、缓存、变更、乐观更新、无限滚动、AI/SSE 响应流、tRPC v11 集成 Router - 基于文件的路由、类型安全导航、经过验证的搜索参数、路由加载器、代码拆分、预加载 Start - SSR/SSG、服务端函数(类型安全的 RPC)、中间件、API 路由、部署到 Cloudflare/Vercel/Node

决策树:

  • 仅客户端的 SPA,带 API 调用 -> Router + Query
  • 带 SSR/服务端函数的全栈应用 -> Start + Query(Start 包含 Router)

TanStack Query v5

设置
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
    },
  },
})

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  )
}
查询
import { useQuery, queryOptions } from '@tanstack/react-query'

// Reusable query definition (recommended pattern)
const todosQueryOptions = queryOptions({
  queryKey: ['todos'],
  queryFn: async () => {
    const res = await fetch('/api/todos')
    if (!res.ok) throw new Error('Failed to fetch')
    return res.json() as Promise<Todo[]>
  },
})

// In component - full type inference from queryOptions
function TodoList() {
  const { data, isLoading, error } = useQuery(todosQueryOptions)
  if (isLoading) return <Spinner />
  if (error) return <div>Error: {error.message}</div>
  return <ul>{data.map(t => <li key={t.id}>{t.title}</li>)}</ul>
}
变更
import { useMutation, useQueryClient } from '@tanstack/react-query'

function CreateTodo() {
  const queryClient = useQueryClient()
  const mutation = useMutation({
    mutationFn: (newTodo: { title: string }) =>
      fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) }).then(r => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  return (
    <button onClick={() => mutation.mutate({ title: 'New' })}>
      {mutation.isPending ? 'Creating...' : 'Create'}
    </button>
  )
}
关键模式

查询键 - 用于缓存管理的分层数组:

['todos']                          // all todos
['todos', 'list', { page, sort }]  // filtered list
['todo', todoId]                   // single item

依赖查询 - 使用 enabled 链接查询:

const { data: user } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
const { data: projects } = useQuery({
  queryKey: ['projects', user?.id],
  queryFn: () => fetchProjects(user!.id),
  enabled: !!user?.id,
})

重要默认值:staleTime: 0,gcTime: 5min,retry: 3,refetchOnWindowFocus: true

Suspense - 在 <Suspense> 边界中使用 useSuspenseQuery

流式查询(实验性)- 用于 AI 聊天/SSE:

import { experimental_streamedQuery as streamedQuery } from '@tanstack/react-query'

const { data: chunks } = useQuery(queryOptions({
  queryKey: ['chat', sessionId],
  queryFn: streamedQuery({ streamFn: () => fetchChatStream(sessionId), refetchMode: 'reset' }),
}))
DevTools
pnpm add @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// Add inside QueryClientProvider
<ReactQueryDevtools initialIsOpen={false} />
Query 深入指南
  • query-guide.md - 完整的 Query 参考,包含所有模式
  • infinite-queries.md - useInfiniteQuery、分页、虚拟滚动
  • optimistic-updates.md - 乐观 UI、回滚、撤销
  • query-performance.md - staleTime 调优、去重、预取
  • query-invalidation.md - 缓存失效策略、筛选器、谓词
  • query-typescript.md - 类型推断、泛型、自定义钩子

TanStack Router v1

设置(Vite)
pnpm add @tanstack/react-router @tanstack/router-plugin
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({ autoCodeSplitting: true }),
    react(),
  ],
})
// src/router.ts
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export const router = createRouter({ routeTree, defaultPreload: 'intent' })

declare module '@tanstack/react-router' {
  interface Register { router: typeof router }
}
基于文件的路由

src/routes/ 中的文件会自动生成路由配置:

约定用途示例
__root.tsx根路由(始终渲染)src/routes/__root.tsx
index.tsx索引路由src/routes/index.tsx -> /
$param动态片段posts.$postId.tsx -> /posts/:id
_prefix无路径布局_layout.tsx 包裹子路由
(folder)路由组(不含 URL)(auth)/login.tsx -> /login
类型安全导航
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>

// Active styling
<Link to="/posts" activeProps={{ className: 'font-bold' }}>Posts</Link>

// Imperative
const navigate = useNavigate({ from: '/posts' })
navigate({ to: '/posts/$postId', params: { postId: post.id } })

始终在 Link 和钩子中提供 from——这会缩小类型范围并提升 TS 性能。

搜索参数
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'

const searchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  sort: fallback(z.enum(['newest', 'oldest']), 'newest').default('newest'),
})

export const Route = createFileRoute('/products')({
  validateSearch: zodValidator(searchSchema),
  component: () => {
    const { page, sort } = Route.useSearch()
    // Writing
    return <Link from={Route.fullPath} search={prev => ({ ...prev, page: prev.page + 1 })}>Next</Link>
  },
})

使用 Zod 适配器(Zod v3)中的 fallback(...).default(...);直接使用 .catch() 会导致类型丢失。使用 Zod v4 时不再需要适配器——直接将 schema 传给 validateSearch,且 .catch() 会保留类型推断。

数据加载
export const Route = createFileRoute('/posts')({
  // loaderDeps: only extract what loader needs (not full search)
  loaderDeps: ({ search: { page } }) => ({ page }),
  loader: ({ deps: { page } }) => fetchPosts({ page }),
  pendingComponent: () => <Spinner />,
  component: () => {
    const posts = Route.useLoaderData()
    return <PostList posts={posts} />
  },
})
路由上下文(依赖注入)
// __root.tsx
interface RouterContext { queryClient: QueryClient }
export const Route = createRootRouteWithContext<RouterContext>()({ component: Root })

// router.ts
const router = createRouter({ routeTree, context: { queryClient } })

// Child route - queryClient available in loader
export const Route = createFileRoute('/posts')({
  loader: ({ context: { queryClient } }) =>
    queryClient.ensureQueryData(postsQueryOptions()),
})
Router 深入指南
  • router-guide.md - 完整的 Router 参考,包含所有模式
  • search-params.md - 自定义序列化、Standard Schema、共享参数
  • data-loading.md - 延迟加载、流式 SSR、shouldReload
  • routing-patterns.md - 虚拟路由、路由遮罩、导航阻止
  • code-splitting.md - 自动/手动拆分策略
  • router-ssr.md - SSR 设置、流式传输、水合

TanStack Start

通过 SSR、服务端函数和中间件扩展 Router 的全栈框架。尚未达到 1.0(API 稳定、功能完备,正在为 1.0 做准备)。React Server Components 作为实验性功能提供——通过 tanstackStart({ rsc: { enabled: true } }) + @vitejs/plugin-rsc 启用(需要 React 19、Vite 7+)。Vite 是默认打包器;也支持 Rsbuild。

设置
npx @tanstack/cli@latest create   # or use TanStack Builder: https://tanstack.com/builder
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    tanstackStart(),
    viteReact(), // MUST come after tanstackStart()
  ],
})
服务端函数

类型安全的 RPC。构建时会从客户端包中提取服务端代码。

import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'

// GET - no input
export const getUsers = createServerFn({ method: 'GET' })
  .handler(async () => db.users.findMany())

// POST - validated input
export const createUser = createServerFn({ method: 'POST' })
  .validator(z.object({ name: z.string(), email: z.string().email() }))
  .handler(async ({ data }) => db.users.create(data))

// Call from loader
export const Route = createFileRoute('/users')({
  loader: () => getUsers(),
  component: () => {
    const users = Route.useLoaderData()
    return <UserList users={users} />
  },
})

重要:加载器是同构的(在服务端和客户端都会运行)。切勿在加载器中放置机密信息——请改用 createServerFn()。

Middleware
import { createMiddleware } from '@tanstack/react-start'

const authMiddleware = createMiddleware({ type: 'function' })
  .server(async ({ next }) => {
    const user = await getCurrentUser()
    if (!user) throw redirect({ to: '/login' })
    return next({ context: { user } })
  })

const getProfile = createServerFn()
  .middleware([authMiddleware])
  .handler(async ({ context }) => context.user) // typed

通过 src/start.ts 配置全局中间件:

export const startInstance = createStart(() => ({
  requestMiddleware: [logger],    // all requests
  functionMiddleware: [auth],     // all server functions
}))

CSRF:只有在不存在 src/start.ts 时,Start 才会自动为服务端函数安装 createCsrfMiddleware()。创建 src/start.ts 后,请显式添加该中间件,否则非 GET 服务端函数将失去同源保护:

requestMiddleware: [createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === 'serverFn' }), logger]
SSR 模式
模式用例
true(默认)SEO、性能
false仅限浏览器的功能
'data-only'仪表板(数据在服务端处理,在客户端渲染)

SPA 模式:在 vite.config.ts 中使用 tanstackStart({ spa: { enabled: true } })

部署
  • 官方合作伙伴:Cloudflare Workers(@cloudflare/vite-plugin)、Netlify(@netlify/vite-plugin-tanstack-start)、Railway
  • Node/Vercel/Bun/Docker:通过 Nitro
  • 静态部署:tanstackStart({ prerender: { enabled: true, crawlLinks: true } })
深入了解 Start
  • start-guide.md - 完整的 Start 参考指南,涵盖所有模式
  • server-functions.md - 流式传输、FormData、渐进增强
  • middleware.md - sendContext、自定义 fetch、全局配置
  • ssr-modes.md - 选择性 SSR、shellComponent、回退渲染
  • server-routes.md - 动态参数、通配符、无路径布局

最佳实践

  1. 使用 queryOptions() 工厂函数定义可复用且类型安全的查询
  2. 按层级组织查询键 - ['entity', 'action', { filters }]
  3. 根据数据类型设置 staleTime - 静态:Infinity,动态:0,中等:5min
  4. 始终使用 Zod 验证搜索参数,通过 zodValidator + fallback().default()
  5. 导航时提供 from - 缩小类型范围,捕获路由不匹配
  6. 使用路由上下文进行依赖注入 - 通过 createRootRouteWithContext 传递 QueryClient 和身份验证信息
  7. 全局设置 defaultPreload: 'intent',提升用户感知的性能
  8. 在数据边界实施身份验证 - 在读取或写入私有数据的服务端函数、服务端路由或 API 端点中进行授权;beforeLoad/路由守卫用于改善用户体验,不是安全边界。切勿将密钥放入同构加载器中 - 请使用 createServerFn()
  9. 按层级组合中间件 - 全局 -> 路由 -> 函数
  10. 在每个内容路由上使用 head() 以优化 SEO(标题、描述、OG 标签)

资源

评论

支持 Markdown