mcpField
S
state-management

state-management

github.com/jmsktm开发1067 次安装41 周前更新
代码仓库

关于 state-management

state-management 是用于为 React 和 Next.js 应用选择及实现状态管理模式的技能,涵盖本地、URL、服务端、Context 和全局状态,并提供表单与渲染性能建议。

主要功能

  1. 01本地、URL 与服务端状态模式
  2. 02React Context 与 Zustand 指引
  3. 03持久化和分片 Store
  4. 04React Hook Form 模式
  5. 05Store 订阅与渲染优化

适用场景

  1. 01为应用功能选择状态范围
  2. 02构建全局设置或用户 Store
  3. 03管理复杂表单状态
  4. 04减少组件不必要的重新渲染

安装

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

$npx skills add https://github.com/jmsktm/claude-settings --skill state-management

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

详细介绍

状态管理技能

概述

此技能可帮助你为 React/Next.js 应用选择并实现合适的状态管理方案。涵盖从本地状态到全局 Store 的所有必备模式。

状态管理层级

1. 本地状态(useState)

用于无需共享的组件专属状态。

'use client'
import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}
2. URL 状态(useSearchParams)

用于需要通过 URL 分享的状态。

'use client'
import { useSearchParams, useRouter } from 'next/navigation'

export function SearchFilter() {
  const router = useRouter()
  const searchParams = useSearchParams()
  const category = searchParams.get('category') || 'all'

  const setCategory = (cat: string) => {
    const params = new URLSearchParams(searchParams)
    params.set('category', cat)
    router.push(`?${params.toString()}`)
  }

  return (
    <select value={category} onChange={(e) => setCategory(e.target.value)}>
      <option value="all">All</option>
      <option value="tech">Tech</option>
      <option value="design">Design</option>
    </select>
  )
}
3. 服务端状态(Server Components)

用于来自数据库/API 且不会在客户端发生变化的数据。

// Server Component (no 'use client')
export default async function UserProfile({ userId }: { userId: string }) {
  const user = await db.users.findUnique({ where: { id: userId } })

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}
4. Context(React Context)

用于组件树中的简单全局状态(主题、用户、设置)。

'use client'
import { createContext, useContext, useState, ReactNode } from 'react'

type Theme = 'light' | 'dark'

const ThemeContext = createContext<{
  theme: Theme
  setTheme: (theme: Theme) => void
}>({ theme: 'light', setTheme: () => {} })

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light')

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  return useContext(ThemeContext)
}

// Usage in component
function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Current: {theme}
    </button>
  )
}
5. Zustand(全局 Store)

用于需要在多个组件间访问的复杂全局状态。

基础 Store:

// stores/user-store.ts
import { create } from 'zustand'

interface UserState {
  user: User | null
  isLoading: boolean
  setUser: (user: User | null) => void
  fetchUser: (id: string) => Promise<void>
  logout: () => void
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  isLoading: false,

  setUser: (user) => set({ user }),

  fetchUser: async (id) => {
    set({ isLoading: true })
    try {
      const response = await fetch(`/api/users/${id}`)
      const user = await response.json()
      set({ user, isLoading: false })
    } catch (error) {
      set({ isLoading: false })
    }
  },

  logout: () => set({ user: null })
}))

// Usage in component
'use client'
import { useUserStore } from '@/stores/user-store'

export function UserProfile() {
  const { user, isLoading, fetchUser } = useUserStore()

  return (
    <div>
      {isLoading ? <p>Loading...</p> : <p>{user?.name}</p>}
    </div>
  )
}

持久化 Store(localStorage):

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface PreferencesState {
  theme: 'light' | 'dark'
  language: string
  setTheme: (theme: 'light' | 'dark') => void
  setLanguage: (lang: string) => void
}

export const usePreferencesStore = create<PreferencesState>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language })
    }),
    {
      name: 'preferences-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
)

切片式 Store(结构化组织):

// stores/slices/auth-slice.ts
export const createAuthSlice = (set, get) => ({
  token: null,
  isAuthenticated: false,
  login: async (credentials) => {
    const token = await loginAPI(credentials)
    set({ token, isAuthenticated: true })
  },
  logout: () => set({ token: null, isAuthenticated: false })
})

// stores/slices/cart-slice.ts
export const createCartSlice = (set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({
    items: [...state.items, item]
  })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)
  })),
  total: () => {
    const items = get().items
    return items.reduce((sum, item) => sum + item.price, 0)
  }
})

// stores/app-store.ts
import { create } from 'zustand'
import { createAuthSlice } from './slices/auth-slice'
import { createCartSlice } from './slices/cart-slice'

export const useAppStore = create((...a) => ({
  ...createAuthSlice(...a),
  ...createCartSlice(...a)
}))

Immer 中间件(不可变更新):

import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

interface TodoState {
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  updateTodo: (id: string, text: string) => void
}

export const useTodoStore = create<TodoState>()(
  immer((set) => ({
    todos: [],

    addTodo: (text) =>
      set((state) => {
        state.todos.push({ id: crypto.randomUUID(), text, done: false })
      }),

    toggleTodo: (id) =>
      set((state) => {
        const todo = state.todos.find((t) => t.id === id)
        if (todo) todo.done = !todo.done
      }),

    updateTodo: (id, text) =>
      set((state) => {
        const todo = state.todos.find((t) => t.id === id)
        if (todo) todo.text = text
      })
  }))
)

表单状态管理

React Hook Form(推荐)
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'

const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().min(18)
})

type FormData = z.infer<typeof schema>

export function UserForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm<FormData>({
    resolver: zodResolver(schema)
  })

  const onSubmit = async (data: FormData) => {
    await fetch('/api/users', {
      method: 'POST',
      body: JSON.stringify(data)
    })
  }

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

      <input {...register('email')} type="email" />
      {errors.email && <p>{errors.email.message}</p>}

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

      <button type="submit" disabled={isSubmitting}>
        Submit
      </button>
    </form>
  )
}

性能优化

选择性订阅 Store
// ❌ Bad - Component re-renders for any store change
function BadExample() {
  const store = useUserStore()
  return <div>{store.user?.name}</div>
}

// ✅ Good - Only re-renders when user.name changes
function GoodExample() {
  const userName = useUserStore((state) => state.user?.name)
  return <div>{userName}</div>
}

// ✅ Better - Use shallow comparison for multiple values
import { shallow } from 'zustand/shallow'

function BetterExample() {
  const { user, isLoading } = useUserStore(
    (state) => ({ user: state.user, isLoading: state.isLoading }),
    shallow
  )
  return <div>{isLoading ? 'Loading...' : user?.name}</div>
}
对组件使用 React.memo
import { memo } from 'react'

const ExpensiveComponent = memo(function ExpensiveComponent({
  data
}: {
  data: Data
}) {
  // This only re-renders when data changes
  return <div>{/* Expensive rendering */}</div>
})
对函数使用 useCallback
'use client'
import { useCallback } from 'react'

export function Parent() {
  const [count, setCount] = useState(0)

  // ❌ Bad - New function on every render
  const handleClick = () => {
    console.log('clicked')
  }

  // ✅ Good - Stable function reference
  const handleClickMemoized = useCallback(() => {
    console.log('clicked')
  }, [])

  return <Child onClick={handleClickMemoized} />
}

高级模式

计算值
import { create } from 'zustand'

interface CartState {
  items: CartItem[]
  // Computed value - always fresh
  total: () => number
  subtotal: () => number
  tax: () => number
}

export const useCartStore = create<CartState>((set, get) => ({
  items: [],

  total: () => {
    const items = get().items
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  },

  subtotal: () => {
    return get().total()
  },

  tax: () => {
    return get().subtotal() * 0.1
  }
}))

// Usage - recalculates on every call
function Cart() {
  const total = useCartStore((state) => state.total())
  return <div>Total: ${total}</div>
}
异步操作
interface DataState {
  data: Data[]
  isLoading: boolean
  error: string | null
  fetchData: () => Promise<void>
  refetch: () => Promise<void>
}

export const useDataStore = create<DataState>((set, get) => ({
  data: [],
  isLoading: false,
  error: null,

  fetchData: async () => {
    set({ isLoading: true, error: null })
    try {
      const response = await fetch('/api/data')
      const data = await response.json()
      set({ data, isLoading: false })
    } catch (error) {
      set({ error: error.message, isLoading: false })
    }
  },

  refetch: async () => {
    await get().fetchData()
  }
}))
中间件组合
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
import { devtools } from 'zustand/middleware'

export const useStore = create<State>()(
  devtools(
    persist(
      immer((set) => ({
        // Your store
      })),
      { name: 'my-store' }
    )
  )
)

决策树

Do you need state?
  ├─ Only in this component? → useState
  ├─ Pass to 2-3 child components? → props
  ├─ Shareable URL? → useSearchParams
  ├─ From database/API?
  │   ├─ Static/rarely changes? → Server Component
  │   └─ Dynamic/frequent updates? → React Query/SWR
  ├─ Simple global (theme, user)? → Context
  └─ Complex global/many subscribers? → Zustand

最佳实践检查清单

  • 从本地状态(useState)开始
  • 使用 URL 状态保存可分享的筛选条件/标签页
  • 数据库数据优先使用 Server Components
  • 使用 Context 管理简单的全局状态
  • 使用 Zustand 管理复杂的全局状态
  • 仅选择所需的 Store 值
  • 对多个值使用浅比较
  • 将用户偏好设置持久化到 localStorage
  • 处理加载和错误状态
  • 对复杂表单使用 React Hook Form
  • 对开销较大的计算进行记忆化
  • 使用 TypeScript 确保类型安全

应避免的常见错误

  1. 过度使用全局状态 - 从本地状态开始,需要时再转为全局状态
  2. 未选择 Store 值 - 始终使用选择器,避免不必要的重新渲染
  3. 存储派生值 - 改为即时计算
  4. 未处理加载状态 - 始终向用户提供反馈
  5. 将所有内容都放进 Zustand - 根据任务选择合适的工具

何时使用此技能

在以下情况下调用此技能:

  • 选择状态管理方案
  • 设置 Zustand Store
  • 优化组件重新渲染
  • 管理表单状态
  • 实现全局用户设置
  • 调试状态相关问题
  • 从 Redux 迁移到 Zustand
  • 设置持久化状态

评论

支持 Markdown