mcpField
S
state-management

state-management

github.com/mgd34msu开发671 次安装11 周前更新
代码仓库

关于 state-management

面向 TypeScript 应用的状态管理规划与实现技能,帮助开发者为服务端、客户端、表单、组件及 URL 状态选择方案,并验证实现。

主要功能

  1. 01发现现有状态库
  2. 02按状态类型选择方案
  3. 03TanStack Query 模式
  4. 04Zustand Store 模式
  5. 05React Hook Form 与 Zod 表单
  6. 06URL 状态实现与验证

适用场景

  1. 01为现有代码库选择状态工具
  2. 02添加带缓存的 API 数据与变更
  3. 03构建复杂的校验表单
  4. 04迁移状态方案并检查实现

安装

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

$npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill state-management

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

附带文件references/state-patterns.mdscripts/validate-state.sh

详细介绍

资源

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

状态管理

本技能将引导你使用 GoodVibes 精准工具进行状态架构决策和实现。在选择状态解决方案、实现数据获取模式或管理复杂表单状态时,请使用此工作流。

何时使用此技能

在以下情况下加载此技能:

  • 决定采用哪种状态管理方式
  • 实现带缓存的服务端数据获取
  • 构建带复杂验证的表单
  • 管理客户端应用状态
  • 实现基于 URL 的状态模式
  • 从一种状态解决方案迁移到另一种

触发短语:“状态管理”、“TanStack Query”、“Zustand”、“React Hook Form”、“表单验证”、“数据获取”、“缓存失效”、“URL 状态”。

核心工作流

阶段 1:探索

选择状态解决方案前,先了解现有模式。

步骤 1.1:识别当前状态库

使用 discover 查找现有的状态管理解决方案。

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 等)
  • 表单处理方式
  • 代码库的一致性
步骤 1.2:分析软件包依赖

使用 precision_read 检查已安装的内容。

precision_read:
  files:
    - path: "package.json"
      extract: content
  verbosity: minimal

查找:

  • @tanstack/react-query(服务端状态)
  • zustand(客户端状态)
  • react-hook-form + zod(表单)
  • nuqs 或类似工具(URL 状态)
步骤 1.3:阅读现有状态模式

阅读 2-3 个示例,了解实现模式。

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:状态分类

为每种状态选择合适的工具。完整决策树请参阅 references/state-patterns.md。

快速决策指南
状态类型最佳工具适用场景
服务端状态TanStack Query来自 API、需要缓存/失效的数据
客户端状态Zustand在多个组件间共享的 UI 状态
表单状态React Hook Form + Zod带验证的复杂表单
URL 状态nuqs 或 searchParams可分享、可加入书签的状态
组件状态useState仅属于单个组件的状态

状态就近放置原则: 尽可能让状态靠近其使用位置。先使用 useState,需要共享时提升到父组件,然后仅在必要时考虑专用解决方案。

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

适用于需要缓存、后台更新和乐观变更的 API 数据。

步骤 3.1:安装依赖

检查是否已安装,否则添加:

npm install @tanstack/react-query  # Note: Targeting TanStack Query v5
npm install -D @tanstack/react-query-devtools
步骤 3.2:设置 Query 客户端

创建 Query 客户端配置。

// 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,
    },
  },
});
步骤 3.3:实现 Query 模式

基本 Query:

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
  });
}

带乐观更新的变更:

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] });
    },
  });
}

缓存失效:

// 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] });

使用错误和加载状态:

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} />;
}
阶段 4:使用 Zustand 管理客户端状态

适用于在多个组件间共享的 UI 状态(模态框、主题、筛选器)。

步骤 4.1:安装依赖
npm install zustand
步骤 4.2:创建 Store

简单 Store:

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:

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' }
    )
  )
);

使用选择器:

// Avoid re-renders by selecting only what you need
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
阶段 5:使用 React Hook Form + Zod 管理表单状态

适用于带验证、字段数组和嵌套对象的复杂表单。

步骤 5.1:安装依赖
npm install react-hook-form zod @hookform/resolvers
步骤 5.2:定义验证架构
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>;
步骤 5.3:实现表单

基本表单:

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>
  );
}

字段数组:

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>
  );
}
阶段 6:URL 状态模式

适用于应当可分享并可加入书签的状态。

步骤 6.1:使用 nuqs(推荐)
npm install nuqs  # Note: Targeting nuqs v1.x
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
}
步骤 6.2:使用 Next.js searchParams
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()}`);
  };
}
阶段 7:使用精准工具实现

使用 precision_write 创建状态管理文件。

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
阶段 8:验证
步骤 8.1:运行验证脚本

使用验证脚本确保质量。

bash scripts/validate-state.sh .

完整验证套件请参阅 scripts/validate-state.sh。

步骤 8.2:类型检查

验证 TypeScript 编译。

precision_exec:
  commands:
    - cmd: "npm run typecheck"
      expect:
        exit_code: 0
  verbosity: minimal

常见模式

模式 1:结合 TanStack Query + Zustand
// Server data with TanStack Query
const { data: user } = useQuery({ queryKey: ['user'], queryFn: getUser });

// UI state with Zustand
const { sidebarOpen, toggleSidebar } = useUIStore();
模式 2:通过服务端变更处理表单
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

const onSubmit = (data: UserFormData) => {
  mutation.mutate(data);
};
模式 3:由 URL 状态驱动数据获取
const [page] = useQueryState('page', parseAsInteger.withDefault(1));
const [search] = useQueryState('search');

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

常见反模式

不要:

  • 使用全局状态存储服务端数据(使用 TanStack Query)
  • 把所有内容都放进 Zustand(尽可能就近存放)
  • 手动管理表单状态(使用 React Hook Form)
  • 在 URL 参数中存储 UI 状态(使用 Zustand)
  • 对频繁变化的数据使用 Context(会导致重新渲染)
  • 在变更后忘记使缓存失效
  • 跳过表单验证方案
  • 在状态存储中使用 any 类型

应该:

  • 根据状态类型选择合适的工具
  • 尽可能就近存放状态
  • 使用选择器避免重新渲染
  • 实施乐观更新以改善 UX
  • 使用 Zod 验证所有表单输入
  • 使用 TypeScript 定义所有状态
  • 在变更后使查询失效
  • 保持查询键一致

快速参考

发现阶段:

discover: { queries: [state_libraries, data_fetching, forms], verbosity: files_only }
precision_read: { files: ["package.json", example stores], extract: content }

实施阶段:

precision_write: { files: [query-client, stores, schemas], verbosity: count_only }

验证阶段:

precision_exec: { commands: [{ cmd: "npm run typecheck" }], verbosity: minimal }

实施后:

bash scripts/validate-state.sh .

有关详细模式和决策树,请参阅 references/state-patterns.md。

评论

支持 Markdown