state-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Resources

资源

scripts/
  validate-state.sh
references/
  state-patterns.md
scripts/
  validate-state.sh
references/
  state-patterns.md

State Management

状态管理

This skill guides you through state architecture decisions and implementation using GoodVibes precision tools. Use this workflow when choosing state solutions, implementing data fetching patterns, or managing complex form state.
本技能将引导你使用GoodVibes精准工具完成状态架构决策与实现。当你需要选择状态管理方案、实现数据获取模式或管理复杂表单状态时,可使用本工作流。

When to Use This Skill

何时使用本技能

Load this skill when:
  • Deciding between state management approaches
  • Implementing server data fetching with caching
  • Building forms with complex validation
  • Managing client-side application state
  • Implementing URL-based state patterns
  • Migrating from one state solution to another
Trigger phrases: "state management", "TanStack Query", "Zustand", "React Hook Form", "form validation", "data fetching", "cache invalidation", "URL state".
在以下场景加载本技能:
  • 选择状态管理方案时
  • 实现带缓存的服务端数据获取时
  • 构建带复杂验证的表单时
  • 管理客户端应用状态时
  • 实现基于URL的状态模式时
  • 从一种状态管理方案迁移到另一种时
触发短语:「state management」「TanStack Query」「Zustand」「React Hook Form」「form validation」「data fetching」「cache invalidation」「URL state」。

Core Workflow

核心工作流

Phase 1: Discovery

第一阶段:发现调研

Before choosing a state solution, understand existing patterns.
在选择状态管理方案前,先了解现有模式。

Step 1.1: Identify Current State Libraries

步骤1.1:识别当前使用的状态管理库

Use
discover
to find existing state management solutions.
yaml
discover:
  queries:
    - id: state_libraries
      type: grep
      pattern: "(from 'zustand'|from '@tanstack/react-query'|from 'react-hook-form'|from 'jotai'|from 'redux'|useContext)"
      glob: "**/*.{ts,tsx}"
    - id: data_fetching
      type: grep
      pattern: "(useQuery|useMutation|useSWR|fetch|axios)"
      glob: "**/*.{ts,tsx}"
    - id: form_libraries
      type: grep
      pattern: "(useForm|Formik|react-hook-form)"
      glob: "**/*.{ts,tsx}"
  verbosity: files_only
What this reveals:
  • Existing state management libraries in use
  • Data fetching patterns (REST, GraphQL, etc.)
  • Form handling approaches
  • Consistency across the codebase
使用
discover
工具查找现有的状态管理解决方案。
yaml
discover:
  queries:
    - id: state_libraries
      type: grep
      pattern: "(from 'zustand'|from '@tanstack/react-query'|from 'react-hook-form'|from 'jotai'|from 'redux'|useContext)"
      glob: "**/*.{ts,tsx}"
    - id: data_fetching
      type: grep
      pattern: "(useQuery|useMutation|useSWR|fetch|axios)"
      glob: "**/*.{ts,tsx}"
    - id: form_libraries
      type: grep
      pattern: "(useForm|Formik|react-hook-form)"
      glob: "**/*.{ts,tsx}"
  verbosity: files_only
该操作可揭示:
  • 当前项目中使用的状态管理库
  • 数据获取模式(REST、GraphQL等)
  • 表单处理方式
  • 代码库的一致性

Step 1.2: Analyze Package Dependencies

步骤1.2:分析包依赖

Use
precision_read
to check what's installed.
yaml
precision_read:
  files:
    - path: "package.json"
      extract: content
  verbosity: minimal
Look for:
  • @tanstack/react-query
    (server state)
  • zustand
    (client state)
  • react-hook-form
    +
    zod
    (forms)
  • nuqs
    or similar (URL state)
使用
precision_read
工具检查已安装的依赖。
yaml
precision_read:
  files:
    - path: "package.json"
      extract: content
  verbosity: minimal
重点查找:
  • @tanstack/react-query
    (服务端状态)
  • zustand
    (客户端状态)
  • react-hook-form
    +
    zod
    (表单)
  • nuqs
    或类似工具(URL状态)

Step 1.3: Read Existing State Patterns

步骤1.3:阅读现有状态实现模式

Read 2-3 examples to understand implementation patterns.
yaml
precision_read:
  files:
    - path: "src/lib/query-client.ts"  # or discovered file
      extract: content
    - path: "src/stores/user-store.ts"  # or discovered file
      extract: content
  verbosity: standard
阅读2-3个示例文件,了解现有实现模式。
yaml
precision_read:
  files:
    - path: "src/lib/query-client.ts"  # 或已发现的对应文件
      extract: content
    - path: "src/stores/user-store.ts"  # 或已发现的对应文件
      extract: content
  verbosity: standard

Phase 2: State Categorization

第二阶段:状态分类

Choose the right tool for each type of state. See
references/state-patterns.md
for the complete decision tree.
为每种类型的状态选择合适的工具。完整决策树请参考
references/state-patterns.md

Quick Decision Guide

快速决策指南

State TypeBest ToolUse When
Server stateTanStack QueryData from APIs, needs caching/invalidation
Client stateZustandUI state shared across components
Form stateReact Hook Form + ZodComplex forms with validation
URL statenuqs or searchParamsSharable, bookmarkable state
Component stateuseStateLocal to one component
State Colocation Principle: Keep state as close to where it's used as possible. Start with
useState
, lift to parent when shared, then consider dedicated solutions only when necessary.
状态类型最佳工具使用场景
服务端状态TanStack QueryAPI数据,需要缓存/失效机制
客户端状态Zustand组件间共享的UI状态
表单状态React Hook Form + Zod带复杂验证的表单
URL状态nuqs 或 searchParams可分享、可收藏的状态
组件内部状态useState仅单个组件使用的本地状态
状态就近原则: 将状态尽可能放在离使用它的位置最近的地方。优先使用
useState
,需要共享时提升到父组件,仅在必要时再考虑专用的状态管理方案。

Phase 3: Server State with TanStack Query

第三阶段:使用TanStack Query管理服务端状态

For data from APIs that needs caching, background updates, and optimistic mutations.
适用于需要缓存、后台更新和乐观更新的API数据。

Step 3.1: Install Dependencies

步骤3.1:安装依赖

Check if installed, otherwise add:
bash
npm install @tanstack/react-query  # Note: Targeting TanStack Query v5
npm install -D @tanstack/react-query-devtools
检查是否已安装,若未安装则执行以下命令:
bash
npm install @tanstack/react-query  # 注意:目标版本为TanStack Query v5
npm install -D @tanstack/react-query-devtools

Step 3.2: Set Up Query Client

步骤3.2:配置Query Client

Create a query client configuration.
typescript
// src/lib/query-client.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60, // 1 minute
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});
创建Query Client配置文件。
typescript
// src/lib/query-client.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60, // 1分钟
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

Step 3.3: Implement Query Patterns

步骤3.3:实现查询模式

Basic Query:
typescript
import { useQuery } from '@tanstack/react-query';
import { getUser } from '@/lib/api';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => getUser(userId),
    enabled: !!userId, // Don't run if no userId
  });
}
Mutation with Optimistic Update:
typescript
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateUser } from '@/lib/api';

export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateUser,
    onMutate: async (newUser) => {
      // Cancel outgoing queries
      await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });

      // Snapshot previous value
      const previousUser = queryClient.getQueryData(['user', newUser.id]);

      // Optimistically update
      queryClient.setQueryData(['user', newUser.id], newUser);

      return { previousUser };
    },
    onError: (err, newUser, context) => {
      // Rollback on error
      queryClient.setQueryData(
        ['user', newUser.id],
        context?.previousUser
      );
    },
    onSettled: (data, error, variables) => {
      // Refetch after error or success
      queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
    },
  });
}
Cache Invalidation:
typescript
// Invalidate all user queries
queryClient.invalidateQueries({ queryKey: ['user'] });

// Invalidate specific user
queryClient.invalidateQueries({ queryKey: ['user', userId] });

// Remove from cache entirely
queryClient.removeQueries({ queryKey: ['user', userId] });
Consuming Error and Loading States:
typescript
function UserProfile({ userId }: { userId: string }) {
  const { data, isPending, isError, error } = useUser(userId);

  if (isPending) return <Skeleton />;
  if (isError) return <ErrorDisplay error={error} />;
  
  return <UserProfile user={data} />;
}
基础查询:
typescript
import { useQuery } from '@tanstack/react-query';
import { getUser } from '@/lib/api';

export function useUser(userId: string) {
  return useQuery({
    queryKey: ['user', userId],
    queryFn: () => getUser(userId),
    enabled: !!userId, // 无userId时不执行
  });
}
带乐观更新的Mutation:
typescript
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateUser } from '@/lib/api';

export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: updateUser,
    onMutate: async (newUser) => {
      // 取消正在进行的相关查询
      await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });

      // 记录之前的数值
      const previousUser = queryClient.getQueryData(['user', newUser.id]);

      // 执行乐观更新
      queryClient.setQueryData(['user', newUser.id], newUser);

      return { previousUser };
    },
    onError: (err, newUser, context) => {
      // 出错时回滚
      queryClient.setQueryData(
        ['user', newUser.id],
        context?.previousUser
      );
    },
    onSettled: (data, error, variables) => {
      // 无论成功或失败,重新获取数据
      queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
    },
  });
}
缓存失效:
typescript
// 使所有用户相关查询失效
queryClient.invalidateQueries({ queryKey: ['user'] });

// 使特定用户的查询失效
queryClient.invalidateQueries({ queryKey: ['user', userId] });

// 从缓存中彻底移除
queryClient.removeQueries({ queryKey: ['user', userId] });
处理错误与加载状态:
typescript
function UserProfile({ userId }: { userId: string }) {
  const { data, isPending, isError, error } = useUser(userId);

  if (isPending) return <Skeleton />;
  if (isError) return <ErrorDisplay error={error} />;
  
  return <UserProfile user={data} />;
}

Phase 4: Client State with Zustand

第四阶段:使用Zustand管理客户端状态

For UI state shared across components (modals, themes, filters).
适用于组件间共享的UI状态(如模态框、主题、筛选条件)。

Step 4.1: Install Dependencies

步骤4.1:安装依赖

bash
npm install zustand
bash
npm install zustand

Step 4.2: Create Store

步骤4.2:创建状态仓库

Simple Store:
typescript
import { create } from 'zustand';

interface UIStore {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
  theme: 'light' | 'dark';
  setTheme: (theme: 'light' | 'dark') => void;
}

export const useUIStore = create<UIStore>((set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
  theme: 'light',
  setTheme: (theme) => set({ theme }),
}));
Store with Slices:
typescript
import { create, StateCreator } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

// Slice pattern for organization
interface AuthSlice {
  user: User | null;
  setUser: (user: User | null) => void;
}

interface UISlice {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
}

type Store = AuthSlice & UISlice;

const createAuthSlice: StateCreator<Store, [], [], AuthSlice> = (set) => ({
  user: null,
  setUser: (user) => set({ user }),
});

const createUISlice: StateCreator<Store, [], [], UISlice> = (set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((state: Store) => ({ 
    sidebarOpen: !state.sidebarOpen 
  })),
});

export const useStore = create<Store>()(
  devtools(
    persist(
      (...a) => ({
        ...createAuthSlice(...a),
        ...createUISlice(...a),
      }),
      { name: 'app-store' }
    )
  )
);
Using Selectors:
typescript
// Avoid re-renders by selecting only what you need
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
简单仓库:
typescript
import { create } from 'zustand';

interface UIStore {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
  theme: 'light' | 'dark';
  setTheme: (theme: 'light' | 'dark') => void;
}

export const useUIStore = create<UIStore>((set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
  theme: 'light',
  setTheme: (theme) => set({ theme }),
}));
分片式仓库:
typescript
import { create, StateCreator } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

// 分片模式用于组织代码
interface AuthSlice {
  user: User | null;
  setUser: (user: User | null) => void;
}

interface UISlice {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
}

type Store = AuthSlice & UISlice;

const createAuthSlice: StateCreator<Store, [], [], AuthSlice> = (set) => ({
  user: null,
  setUser: (user) => set({ user }),
});

const createUISlice: StateCreator<Store, [], [], UISlice> = (set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((state: Store) => ({ 
    sidebarOpen: !state.sidebarOpen 
  })),
});

export const useStore = create<Store>()(
  devtools(
    persist(
      (...a) => ({
        ...createAuthSlice(...a),
        ...createUISlice(...a),
      }),
      { name: 'app-store' }
    )
  )
);
使用选择器:
typescript
// 通过选择器仅获取需要的状态,避免不必要的重渲染
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar);

Phase 5: Form State with React Hook Form + Zod

第五阶段:使用React Hook Form + Zod管理表单状态

For complex forms with validation, field arrays, and nested objects.
适用于带复杂验证、字段数组和嵌套对象的表单。

Step 5.1: Install Dependencies

步骤5.1:安装依赖

bash
npm install react-hook-form zod @hookform/resolvers
bash
npm install react-hook-form zod @hookform/resolvers

Step 5.2: Define Validation Schema

步骤5.2:定义验证Schema

typescript
import { z } from 'zod';

export const userSchema = z.object({
  email: z.string().email('Invalid email address'),
  name: z.string().min(2, 'Name must be at least 2 characters'),
  age: z.number().min(18, 'Must be 18 or older'),
  role: z.enum(['user', 'admin']).default('user'),
  preferences: z.object({
    newsletter: z.boolean().default(false),
    notifications: z.boolean().default(true),
  }),
});

export type UserFormData = z.infer<typeof userSchema>;
typescript
import { z } from 'zod';

export const userSchema = z.object({
  email: z.string().email('无效的邮箱地址'),
  name: z.string().min(2, '姓名至少需要2个字符'),
  age: z.number().min(18, '必须年满18岁'),
  role: z.enum(['user', 'admin']).default('user'),
  preferences: z.object({
    newsletter: z.boolean().default(false),
    notifications: z.boolean().default(true),
  }),
});

export type UserFormData = z.infer<typeof userSchema>;

Step 5.3: Implement Form

步骤5.3:实现表单

Basic Form:
typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { userSchema, type UserFormData } from './schema';

export function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<UserFormData>({
    resolver: zodResolver(userSchema),
    defaultValues: {
      role: 'user',
      preferences: {
        newsletter: false,
        notifications: true,
      },
    },
  });

  const onSubmit = async (data: UserFormData) => {
    await createUser(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register('name')} />
      {errors.name && <span>{errors.name.message}</span>}

      <input type="number" {...register('age', { valueAsNumber: true })} />
      {errors.age && <span>{errors.age.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}
Field Arrays:
typescript
import { useFieldArray } from 'react-hook-form';

const schema = z.object({
  users: z.array(
    z.object({
      name: z.string().min(1),
      email: z.string().email(),
    })
  ).min(1, 'At least one user required'),
});

function UsersForm() {
  const { control, register } = useForm({
    resolver: zodResolver(schema),
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'users',
  });

  return (
    <div>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input {...register(`users.${index}.name`)} />
          <input {...register(`users.${index}.email`)} />
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() => append({ name: '', email: '' })}
      >
        Add User
      </button>
    </div>
  );
}
基础表单:
typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { userSchema, type UserFormData } from './schema';

export function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<UserFormData>({
    resolver: zodResolver(userSchema),
    defaultValues: {
      role: 'user',
      preferences: {
        newsletter: false,
        notifications: true,
      },
    },
  });

  const onSubmit = async (data: UserFormData) => {
    await createUser(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register('name')} />
      {errors.name && <span>{errors.name.message}</span>}

      <input type="number" {...register('age', { valueAsNumber: true })} />
      {errors.age && <span>{errors.age.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '提交'}
      </button>
    </form>
  );
}
字段数组:
typescript
import { useFieldArray } from 'react-hook-form';

const schema = z.object({
  users: z.array(
    z.object({
      name: z.string().min(1),
      email: z.string().email(),
    })
  ).min(1, '至少需要添加一个用户'),
});

function UsersForm() {
  const { control, register } = useForm({
    resolver: zodResolver(schema),
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'users',
  });

  return (
    <div>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input {...register(`users.${index}.name`)} />
          <input {...register(`users.${index}.email`)} />
          <button type="button" onClick={() => remove(index)}>
            删除
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() => append({ name: '', email: '' })}
      >
        添加用户
      </button>
    </div>
  );
}

Phase 6: URL State Patterns

第六阶段:URL状态模式

For state that should be shareable and bookmarkable.
适用于需要分享和收藏的状态。

Step 6.1: Using nuqs (Recommended)

步骤6.1:使用nuqs(推荐)

bash
npm install nuqs  # Note: Targeting nuqs v1.x
typescript
import { useQueryState, parseAsInteger, parseAsStringEnum } from 'nuqs';

export function ProductList() {
  const [page, setPage] = useQueryState(
    'page',
    parseAsInteger.withDefault(1)
  );

  const [sort, setSort] = useQueryState(
    'sort',
    parseAsStringEnum(['name', 'price', 'date']).withDefault('name')
  );

  // URL: /products?page=2&sort=price
  // Automatically synced, type-safe, bookmarkable
}
bash
npm install nuqs  # 注意:目标版本为nuqs v1.x
typescript
import { useQueryState, parseAsInteger, parseAsStringEnum } from 'nuqs';

export function ProductList() {
  const [page, setPage] = useQueryState(
    'page',
    parseAsInteger.withDefault(1)
  );

  const [sort, setSort] = useQueryState(
    'sort',
    parseAsStringEnum(['name', 'price', 'date']).withDefault('name')
  );

  // URL示例:/products?page=2&sort=price
  // 自动同步、类型安全、可收藏
}

Step 6.2: Using Next.js searchParams

步骤6.2:使用Next.js的searchParams

typescript
import { useSearchParams, useRouter } from 'next/navigation';

export function ProductList() {
  const searchParams = useSearchParams();
  const router = useRouter();

  const page = Number(searchParams.get('page')) || 1;

  const setPage = (newPage: number) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', String(newPage));
    router.push(`?${params.toString()}`);
  };
}
typescript
import { useSearchParams, useRouter } from 'next/navigation';

export function ProductList() {
  const searchParams = useSearchParams();
  const router = useRouter();

  const page = Number(searchParams.get('page')) || 1;

  const setPage = (newPage: number) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', String(newPage));
    router.push(`?${params.toString()}`);
  };
}

Phase 7: Implementation with Precision Tools

第七阶段:使用精准工具实现

Use
precision_write
to create state management files.
yaml
precision_write:
  files:
    - path: "src/lib/query-client.ts"
      content: |
        import { QueryClient } from '@tanstack/react-query';
        export const queryClient = new QueryClient({ ... });
    - path: "src/stores/ui-store.ts"
      content: |
        import { create } from 'zustand';
        export const useUIStore = create({ ... });
    - path: "src/schemas/user-schema.ts"
      content: |
        import { z } from 'zod';
        export const userSchema = z.object({ ... });
  verbosity: count_only
使用
precision_write
工具创建状态管理相关文件。
yaml
precision_write:
  files:
    - path: "src/lib/query-client.ts"
      content: |
        import { QueryClient } from '@tanstack/react-query';
        export const queryClient = new QueryClient({ ... });
    - path: "src/stores/ui-store.ts"
      content: |
        import { create } from 'zustand';
        export const useUIStore = create({ ... });
    - path: "src/schemas/user-schema.ts"
      content: |
        import { z } from 'zod';
        export const userSchema = z.object({ ... });
  verbosity: count_only

Phase 8: Validation

第八阶段:验证

Step 8.1: Run Validation Script

步骤8.1:运行验证脚本

Use the validation script to ensure quality.
bash
bash scripts/validate-state.sh .
See
scripts/validate-state.sh
for the complete validation suite.
使用验证脚本确保代码质量。
bash
bash scripts/validate-state.sh .
完整验证套件请查看
scripts/validate-state.sh

Step 8.2: Type Check

步骤8.2:类型检查

Verify TypeScript compilation.
yaml
precision_exec:
  commands:
    - cmd: "npm run typecheck"
      expect:
        exit_code: 0
  verbosity: minimal
验证TypeScript编译是否通过。
yaml
precision_exec:
  commands:
    - cmd: "npm run typecheck"
      expect:
        exit_code: 0
  verbosity: minimal

Common Patterns

常见模式

Pattern 1: Combine TanStack Query + Zustand

模式1:结合TanStack Query + Zustand

typescript
// Server data with TanStack Query
const { data: user } = useQuery({ queryKey: ['user'], queryFn: getUser });

// UI state with Zustand
const { sidebarOpen, toggleSidebar } = useUIStore();
typescript
// 使用TanStack Query获取服务端数据
const { data: user } = useQuery({ queryKey: ['user'], queryFn: getUser });

// 使用Zustand管理UI状态
const { sidebarOpen, toggleSidebar } = useUIStore();

Pattern 2: Form with Server Mutation

模式2:表单结合服务端Mutation

typescript
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

const onSubmit = (data: UserFormData) => {
  mutation.mutate(data);
};
typescript
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

const onSubmit = (data: UserFormData) => {
  mutation.mutate(data);
};

Pattern 3: URL State Driving Data Fetching

模式3:URL状态驱动数据获取

typescript
const [page] = useQueryState('page', parseAsInteger.withDefault(1));
const [search] = useQueryState('search');

const { data } = useQuery({
  queryKey: ['products', page, search],
  queryFn: () => getProducts({ page, search }),
});
typescript
const [page] = useQueryState('page', parseAsInteger.withDefault(1));
const [search] = useQueryState('search');

const { data } = useQuery({
  queryKey: ['products', page, search],
  queryFn: () => getProducts({ page, search }),
});

Common Anti-Patterns

常见反模式

DON'T:
  • Use global state for server data (use TanStack Query)
  • Put everything in Zustand (colocate when possible)
  • Manage form state manually (use React Hook Form)
  • Store UI state in URL params (use Zustand)
  • Use Context for frequently changing data (causes re-renders)
  • Forget to invalidate cache after mutations
  • Skip validation schemas for forms
  • Use
    any
    types in state stores
DO:
  • Match state type to the right tool
  • Colocate state when possible
  • Use selectors to prevent re-renders
  • Implement optimistic updates for better UX
  • Validate all form inputs with Zod
  • Use TypeScript for all state definitions
  • Invalidate queries after mutations
  • Keep query keys consistent
请勿:
  • 使用全局状态管理服务端数据(应使用TanStack Query)
  • 将所有状态都放入Zustand(尽可能就近存放)
  • 手动管理表单状态(应使用React Hook Form)
  • 将UI状态存储在URL参数中(应使用Zustand)
  • 使用Context管理频繁变化的数据(会导致不必要的重渲染)
  • 执行Mutation后忘记失效缓存
  • 不为表单设置验证Schema
  • 在状态仓库中使用
    any
    类型
建议:
  • 为不同类型的状态选择合适的工具
  • 尽可能就近存放状态
  • 使用选择器避免不必要的重渲染
  • 实现乐观更新以提升用户体验
  • 使用Zod验证所有表单输入
  • 为所有状态定义使用TypeScript
  • 执行Mutation后失效相关查询
  • 保持查询键的一致性

Quick Reference

快速参考

Discovery Phase:
yaml
discover: { queries: [state_libraries, data_fetching, forms], verbosity: files_only }
precision_read: { files: ["package.json", example stores], extract: content }
Implementation Phase:
yaml
precision_write: { files: [query-client, stores, schemas], verbosity: count_only }
Validation Phase:
yaml
precision_exec: { commands: [{ cmd: "npm run typecheck" }], verbosity: minimal }
Post-Implementation:
bash
bash scripts/validate-state.sh .
For detailed patterns and decision trees, see
references/state-patterns.md
.
发现调研阶段:
yaml
discover: { queries: [state_libraries, data_fetching, forms], verbosity: files_only }
precision_read: { files: ["package.json", 示例状态仓库文件], extract: content }
实现阶段:
yaml
precision_write: { files: [query-client, 状态仓库, 验证Schema], verbosity: count_only }
验证阶段:
yaml
precision_exec: { commands: [{ cmd: "npm run typecheck" }], verbosity: minimal }
实现后:
bash
bash scripts/validate-state.sh .
详细模式和决策树请参考
references/state-patterns.md