Skip to main content

state-management

Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue.

来源信息

仓库
MadAppGang/claude-code
最近来源活动
2026年1月20日 08:16
检测到的 SKILL.md 语言
英语
星标
284
分支
25

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
state-management
version
1.0.0
description
Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue.
keywords
["state management","Zustand","Pinia","TanStack Query","server state","global state","URL state","React context"]
plugin
dev
updated
2026-01-20T00:00:00.000Z
# Frontend State Management ## Overview Patterns and best practices for managing state in frontend applications across different frameworks. ## State Categories ### Local vs Global State | Type | Scope | Examples | Solution | |------|-------|----------|----------| | **Local UI** | Single component | Form inputs, modals, dropdowns | useState, ref | | **Shared UI** | Component subtree | Theme, sidebar state | Context, provide/inject | | **Server** | Cached API data | Users, products, orders | TanStack Query, SWR | | **Global App** | Entire app | Auth, settings, notifications | Zustand, Pinia, Redux | | **URL** | Browser URL | Filters, pagination, search | Router params/query | ### When to Use What ``` ┌─────────────────────────────────────────────────────────┐ │ Does only this component need it? │ │ YES → Local state (useState/ref) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it server data that needs caching/sync? │ │ YES → Server state library (TanStack Query) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it in the URL (shareable state)? │ │ YES → URL state (router) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it needed across unrelated components? │ │ YES → Global store (Zustand/Pinia) │ │ NO → Lift state up or Context │ └─────────────────────────────────────────────────────────┘ ``` ## Server State (TanStack Query) ### Basic Query Pattern ```tsx // Define query function useUsers(filters: UserFilters) { return useQuery({ queryKey: ['users', filters], queryFn: () => api.getUsers(filters), staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 30 * 60 * 1000, // 30 minutes }); } // Use in component function UserList() { const [filters, setFilters] = useState<UserFilters>({}); const { data, isLoading, error } = useUsers(filters); if (isLoading) return <Spinner />; if (error) return <Error message={error.message} />; return <List items={data} />; } ``` ### Mutation Pattern ```tsx function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateUserInput) => api.createUser(data), onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } // Usage const createUser = useCreateUser(); await createUser.mutateAsync({ name: 'John', email: 'john@example.com' }); ``` ### Optimistic Updates ```tsx function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: UpdateUserInput) => api.updateUser(data), onMutate: async (newData) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['user', newData.id] }); // Snapshot previous value const previous = queryClient.getQueryData(['user', newData.id]); // Optimistically update queryClient.setQueryData(['user', newData.id], (old: User) => ({ ...old, ...newData, })); return { previous }; }, onError: (err, newData, context) => { // Rollback on error queryClient.setQueryData(['user', newData.id], context?.previous); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } ``` ## Global State (Zustand) ### Store Definition ```tsx interface AppStore { // State theme: 'light' | 'dark'; sidebarOpen: boolean; notifications: Notification[]; // Actions setTheme: (theme: 'light' | 'dark') => void; toggleSidebar: () => void; addNotification: (notification: Notification) => void; removeNotification: (id: string) => void; } export const useAppStore = create<AppStore>((set) => ({ theme: 'light', sidebarOpen: true, notifications: [], setTheme: (theme) => set({ theme }), toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), addNotification: (notification) => set((state) => ({ notifications: [...state.notifications, notification], })), removeNotification: (id) => set((state) => ({ notifications: state.notifications.filter((n) => n.id !== id), })), })); ``` ### Selectors for Performance ```tsx // BAD: Re-renders on any store change const { theme, notifications } = useAppStore(); // GOOD: Only re-renders when theme changes const theme = useAppStore((state) => state.theme); // GOOD: Multiple selectors with shallow comparison const { theme, sidebarOpen } = useAppStore( (state) => ({ theme: state.theme, sidebarOpen: state.sidebarOpen }), shallow ); ``` ### Computed/Derived State ```tsx const useAppStore = create<AppStore>((set, get) => ({ notifications: [], // Derived state as selector unreadCount: () => get().notifications.filter((n) => !n.read).length, // Or compute in selector })); // Usage with computed const unreadCount = useAppStore((state) => state.notifications.filter((n) => !n.read).length ); ``` ## Global State (Pinia for Vue) ```ts export const useAppStore = defineStore('app', () => { // State const theme = ref<'light' | 'dark'>('light'); const sidebarOpen = ref(true); const notifications = ref<Notification[]>([]); // Getters (computed) const unreadCount = computed(() => notifications.value.filter((n) => !n.read).length ); // Actions function setTheme(newTheme: 'light' | 'dark') { theme.value = newTheme; } function toggleSidebar() { sidebarOpen.value = !sidebarOpen.value; } return { theme, sidebarOpen, notifications, unreadCount, setTheme, toggleSidebar, }; }); ``` ## URL State ### Search Params ```tsx // React with react-router function useSearchParams() { const [searchParams, setSearchParams] = useSearchParams(); const filters = useMemo( () => ({ page: parseInt(searchParams.get('page') || '1'), search: searchParams.get('search') || '', sort: searchParams.get('sort') || 'name', }), [searchParams] ); const setFilters = (newFilters: Partial<typeof filters>) => { setSearchParams((prev) => { Object.entries(newFilters).forEach(([key, value]) => { if (value) prev.set(key, String(value)); else prev.delete(key); }); return prev; }); }; return [filters, setFilters] as const; } ``` ### Benefits of URL State - Shareable links - Browser back/forward works - Bookmarkable - SEO-friendly - Survives refresh ## Best Practices ### 1. Colocate State Keep state as close to where it's used as possible. ```tsx // BAD: Global state for local concern const useGlobalStore = create(() => ({ isModalOpen: false, toggleModal: () => {}, })); // GOOD: Local state for local concern function UserProfile() { const [isModalOpen, setModalOpen] = useState(false); } ``` ### 2. Single Source of Truth Don't duplicate state across stores. ```tsx // BAD: Duplicated user in multiple places const authStore = { user: { id: 1, name: 'John' } }; const profileStore = { user: { id: 1, name: 'John' } }; // Duplicated! // GOOD: Reference by ID const authStore = { userId: 1 }; const usersCache = { 1: { id: 1, name: 'John' } }; ``` ### 3. Derive Don't Store Compute derived data instead of storing it. ```tsx // BAD: Storing derived state const store = { items: [], itemCount: 0, // Derived! filteredItems: [], // Derived! totalPrice: 0, // Derived! }; // GOOD: Compute on demand const store = { items: [], }; // Compute when needed const itemCount = items.length; const filteredItems = items.filter(predicate); const totalPrice = items.reduce((sum, i) => sum + i.price, 0); ``` ### 4. Normalize Complex Data ```tsx // BAD: Nested data const store = { orders: [ { id: 1, user: { id: 1, name: 'John' }, items: [{ id: 1, product: { id: 1, name: 'Widget' } }], }, ], }; // GOOD: Normalized const store = { orders: { 1: { id: 1, userId: 1, itemIds: [1] } }, users: { 1: { id: 1, name: 'John' } }, items: { 1: { id: 1, productId: 1, orderId: 1 } }, products: { 1: { id: 1, name: 'Widget' } }, }; ``` ### 5. Handle Loading/Error States ```tsx interface AsyncState<T> { data: T | null; loading: boolean; error: Error | null; } // Always handle all three states function UserList({ state }: { state: AsyncState<User[]> }) { if (state.loading) return <Spinner />; if (state.error) return <Error error={state.error} />; if (!state.data?.length) return <Empty />; return <List items={state.data} />; } ``` ## Anti-Patterns ### 1. Prop Drilling (Instead: Context or Store) ```tsx // BAD: Passing through many levels <App theme={theme}> <Layout theme={theme}> <Sidebar theme={theme}> <MenuItem theme={theme} /> // 4 levels deep! </Sidebar> </Layout> </App> // GOOD: Context or store const theme = useTheme(); // Access anywhere ``` ### 2. Storing Server Data in Global State ```tsx // BAD: Manual cache management const store = { users: [], fetchUsers: async () => { const users = await api.getUsers(); set({ users }); }, }; // GOOD: Use server state library const { data: users } = useQuery(['users'], api.getUsers); ``` ### 3. Mutating State Directly ```tsx // BAD: Direct mutation state.users.push(newUser); state.users[0].name = 'Updated'; // GOOD: Immutable updates set({ users: [...state.users, newUser] }); set({ users: state.users.map((u) => u.id === id ? { ...u, name: 'Updated' } : u ), }); ``` --- *State management patterns for frontend applications*
在 GitHub 查看