tanstack
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTanStack Developer Guide
TanStack开发者指南
This skill provides comprehensive patterns and best practices for the TanStack ecosystem in React applications:
- TanStack Query/DB: Data fetching, caching, collections, live queries, and optimistic updates
- TanStack Form: Form state management, validation, and field components
- TanStack Router: File-based routing, type-safe navigation, and URL parameters
本技能提供了在React应用中使用TanStack生态的全面模式和最佳实践:
- TanStack Query/DB:数据获取、缓存、集合、实时查询和乐观更新
- TanStack Form:表单状态管理、校验和字段组件
- TanStack Router:基于文件的路由、类型安全导航和URL参数
Quick Start
快速开始
For detailed examples and patterns, refer to the following files in the directory:
references/- - TanStack Query and TanStack DB patterns
references/query-patterns.md - - TanStack Form patterns and components
references/form-patterns.md - - TanStack Router patterns and navigation
references/router-patterns.md
如需查看详细示例和模式,请参考目录下的以下文件:
references/- - TanStack Query 和 TanStack DB 模式
references/query-patterns.md - - TanStack Form 模式和组件
references/form-patterns.md - - TanStack Router 模式和导航
references/router-patterns.md
TanStack Query/DB Overview
TanStack Query/DB 概述
TanStack DB extends TanStack Query with collections, live queries, and optimistic mutations. Key principle: load data into typed collections and consume through live queries that auto-update on data changes.
TanStack DB 在 TanStack Query 的基础上扩展了集合、实时查询和乐观变更能力。核心原则:将数据加载到有类型定义的集合中,通过实时查询消费数据,数据变更时会自动更新。
Critical Rules
关键规则
-
Never Use React Query Patterns with Collections - Collections have built-in mutation handling. Do NOT use+
useMutation.invalidateQueries -
Always Share Collection Instances - Creating new collection instances for mutations causes "key not found" errors. The data-fetching hook must expose the collection, and mutation hooks must receive it as a parameter.
-
Configure Persistence Handlers - Put server writes in collection handlers (,
onInsert,onUpdate), not mutation hooks.onDelete -
Single Canonical Collection Pattern - Create ONE collection per entity type. Use live queries for filtered views.
-
Check Field Changes Properly - Verify fields actually changed in, not just that they exist.
onUpdate
-
永远不要对集合使用React Query模式 - 集合内置了变更处理能力。不要使用+
useMutation。invalidateQueries -
始终共享集合实例 - 为变更操作创建新的集合实例会导致"key not found"错误。数据获取Hook必须暴露集合实例,变更Hook需要将其作为参数接收。
-
配置持久化处理器 - 将服务端写入逻辑放在集合处理器(、
onInsert、onUpdate)中,不要放在变更Hook里。onDelete -
单规范集合模式 - 每个实体类型只创建一个集合,使用实时查询实现过滤视图。
-
正确校验字段变更 - 在中校验字段是否确实发生了变更,而不是仅校验字段是否存在。
onUpdate
Basic Collection Setup
基础集合设置
typescript
import { createCollection } from '@tanstack/react-db';
import { queryCollectionOptions } from '@tanstack/query-db-collection';
import { z } from 'zod';
const itemSchema = z.object({
id: z.string(),
name: z.string().min(1),
status: z.enum(['active', 'archived']),
});
const itemCollection = createCollection(
queryCollectionOptions({
queryKey: ['items'],
queryFn: async () => (await fetch('/api/items')).json(),
queryClient,
getKey: (item) => item.id,
schema: itemSchema,
})
);typescript
import { createCollection } from '@tanstack/react-db';
import { queryCollectionOptions } from '@tanstack/query-db-collection';
import { z } from 'zod';
const itemSchema = z.object({
id: z.string(),
name: z.string().min(1),
status: z.enum(['active', 'archived']),
});
const itemCollection = createCollection(
queryCollectionOptions({
queryKey: ['items'],
queryFn: async () => (await fetch('/api/items')).json(),
queryClient,
getKey: (item) => item.id,
schema: itemSchema,
})
);Sharing Collection Instance (Critical Pattern)
共享集合实例(关键模式)
typescript
// CORRECT - share the instance
export function useItems() {
const collection = useMemo(() => createItemsCollection(), []);
const { data } = useLiveQuery(collection);
return { data, collection }; // Expose collection
}
export function useUpdateItem(collection: ItemsCollection) {
return (id, data) => collection.update(id, data);
}typescript
// CORRECT - share the instance
export function useItems() {
const collection = useMemo(() => createItemsCollection(), []);
const { data } = useLiveQuery(collection);
return { data, collection }; // Expose collection
}
export function useUpdateItem(collection: ItemsCollection) {
return (id, data) => collection.update(id, data);
}TanStack Form Overview
TanStack Form 概述
TanStack Form provides headless form logic with automatic type inference and flexible validation.
TanStack Form 提供无UI的表单逻辑,具备自动类型推断和灵活的校验能力。
Core Principles
核心原则
- Type Safety: Types are inferred from default values - avoid manual generic declarations.
- Headless Design: Build UI components to match your design system.
- Schema-First Validation: Use Zod for cleaner, more maintainable validation.
- 类型安全:类型从默认值自动推断,避免手动声明泛型。
- 无UI设计:可构建符合自身设计系统的UI组件。
- Schema优先的校验:使用Zod实现更简洁、可维护性更高的校验。
Basic Form Setup with createFormHook
createFormHook使用createFormHook
的基础表单设置
createFormHooktypescript
import { createFormHookContexts, createFormHook } from '@tanstack/react-form'
export const { fieldContext, formContext, useFieldContext } =
createFormHookContexts()
export const { useAppForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField,
SelectField,
},
formComponents: {
SubmitButton,
},
})typescript
import { createFormHookContexts, createFormHook } from '@tanstack/react-form'
export const { fieldContext, formContext, useFieldContext } =
createFormHookContexts()
export const { useAppForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField,
SelectField,
},
formComponents: {
SubmitButton,
},
})Form Initialization
表单初始化
typescript
const form = useAppForm({
defaultValues: {
username: '',
email: '',
age: 0,
},
validators: {
onChange: schema,
},
onSubmit: async ({ value }) => {
// Handle submission
},
})typescript
const form = useAppForm({
defaultValues: {
username: '',
email: '',
age: 0,
},
validators: {
onChange: schema,
},
onSubmit: async ({ value }) => {
// Handle submission
},
})Async Validation with Debouncing
带防抖的异步校验
typescript
<form.Field
name="username"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
const isAvailable = await checkUsernameAvailability(value)
return isAvailable ? undefined : 'Username already taken'
},
}}
/>typescript
<form.Field
name="username"
asyncDebounceMs={500}
validators={{
onChangeAsync: async ({ value }) => {
const isAvailable = await checkUsernameAvailability(value)
return isAvailable ? undefined : 'Username already taken'
},
}}
/>TanStack Router Overview
TanStack Router 概述
TanStack Router provides type-safe file-based routing with first-class TypeScript support.
TanStack Router 提供类型安全的基于文件的路由,具备一流的TypeScript支持。
Core Principles
核心原则
- Type-Safe Routing: Embrace type-safe routing as the primary benefit.
- File-Based Routes: Use file-based routing for scalability.
- Generated Route Tree: Leverage the generated route tree for type safety.
- 类型安全路由:将类型安全路由作为核心优势使用。
- 基于文件的路由:使用基于文件的路由实现可扩展性。
- 生成路由树:利用生成的路由树保证类型安全。
File Structure
文件结构
src/routes/
├── __root.tsx # Root layout with providers
├── _authenticated.tsx # Auth layout wrapper
├── index.tsx # Home page (/)
├── posts/
│ ├── index.tsx # /posts
│ └── $postId.tsx # /posts/:postId (typed params)
└── settings/
├── _layout.tsx # Settings layout
└── profile.tsx # /settings/profilesrc/routes/
├── __root.tsx # Root layout with providers
├── _authenticated.tsx # Auth layout wrapper
├── index.tsx # Home page (/)
├── posts/
│ ├── index.tsx # /posts
│ └── $postId.tsx # /posts/:postId (typed params)
└── settings/
├── _layout.tsx # Settings layout
└── profile.tsx # /settings/profileBasic Route with Search Params
带搜索参数的基础路由
typescript
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const searchSchema = z.object({
page: z.number().min(1).catch(1),
search: z.string().optional(),
})
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
component: PostsList,
})
function PostsList() {
const { page, search } = Route.useSearch()
// Use search params...
}typescript
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const searchSchema = z.object({
page: z.number().min(1).catch(1),
search: z.string().optional(),
})
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
component: PostsList,
})
function PostsList() {
const { page, search } = Route.useSearch()
// Use search params...
}Authentication Layout
鉴权布局
typescript
// routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: async ({ location }) => {
const isAuthenticated = checkAuth()
if (!isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => <Outlet />,
})typescript
// routes/_authenticated.tsx
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: async ({ location }) => {
const isAuthenticated = checkAuth()
if (!isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => <Outlet />,
})Type-Safe Navigation
类型安全导航
typescript
import { Link, useNavigate } from '@tanstack/react-router'
function Navigation() {
const navigate = useNavigate()
return (
<>
<Link
to="/posts/$postId"
params={{ postId: '123' }}
search={{ tab: 'comments' }}
>
View Post
</Link>
<button onClick={() => navigate({ to: '/posts', search: { page: 1 } })}>
Go to Posts
</button>
</>
)
}typescript
import { Link, useNavigate } from '@tanstack/react-router'
function Navigation() {
const navigate = useNavigate()
return (
<>
<Link
to="/posts/$postId"
params={{ postId: '123' }}
search={{ tab: 'comments' }}
>
View Post
</Link>
<button onClick={() => navigate({ to: '/posts', search: { page: 1 } })}>
Go to Posts
</button>
</>
)
}Validation Checklist
校验清单
Before finishing a task involving TanStack:
完成涉及TanStack的任务前,请检查:
Query/DB
Query/DB
- Collection instances are shared between data-fetching and mutation hooks
- Persistence handlers (,
onInsert,onUpdate) are configuredonDelete - No +
useMutationpatterns with collectionsinvalidateQueries - One canonical collection per entity type
- Field changes properly verified in handlers
onUpdate
- 集合实例在数据获取Hook和变更Hook之间共享
- 已配置持久化处理器(、
onInsert、onUpdate)onDelete - 未对集合使用+
useMutation模式invalidateQueries - 每个实体类型仅对应一个规范集合
- 在处理器中正确校验字段变更
onUpdate
Form
Form
- Use with
createFormHookinstead of rawuseAppFormfor consistencyuseForm - Provide complete default values for proper type inference
- Use Zod schemas for validation when possible
- Debounce async validations (minimum 500ms recommended)
- Prevent default on form submission
- Display errors with proper accessibility ()
role="alert"
- 统一使用搭配
createFormHook,而非直接使用原生useAppFormuseForm - 提供完整的默认值以保证正确的类型推断
- 尽可能使用Zod schema进行校验
- 异步校验添加防抖(推荐最低500ms)
- 表单提交时阻止默认行为
- 错误提示具备合理的无障碍属性()
role="alert"
Router
Router
- Route path in matches file location
createFileRoute - Search params use Zod validation with proper defaults ()
.catch() - Loader dependencies are correctly specified in
loaderDeps - Authentication routes use with proper redirects
beforeLoad - Navigation uses typed or
LinkhooksuseNavigate - Error boundaries are implemented at route level
- 中的路由路径与文件位置匹配
createFileRoute - 搜索参数使用Zod校验并配置合理的默认值()
.catch() - 加载器依赖已在中正确声明
loaderDeps - 鉴权路由使用并配置正确的重定向逻辑
beforeLoad - 导航使用带类型的或
LinkHookuseNavigate - 路由层面已实现错误边界
General
通用
- Run and
pnpm run typecheckpnpm run test
For complete examples, edge cases, and advanced patterns, see the reference files in this skill directory.
- 执行和
pnpm run typecheckpnpm run test
如需完整示例、边缘场景和高级模式,请查看本技能目录下的参考文件。