# 状态管理设计文档 ## 1. 状态管理方案选择 ### 1.1 为什么选择Context API 对于本项目的需求,React Context API是最佳选择: **优势:** - 轻量级,无需引入额外的库 - React官方内置,学习成本低 - 适合中小型应用的状态管理 - 与React生态系统完美集成 **适用场景:** - 认证状态(用户信息、Token) - 全局配置(主题、语言) - 简单的共享状态 **不适用场景:** - 复杂的跨组件通信 - 高频率的状态更新 - 需要时间旅行调试 - 如果将来需要,可以迁移到Redux或Zustand ### 1.2 状态管理架构 ``` ┌─────────────────────────────────────────┐ │ App Root │ ├─────────────────────────────────────────┤ │ AuthProvider (认证状态) │ │ ├─ user (用户信息) │ │ ├─ token (认证令牌) │ │ └─ authState (认证状态) │ │ │ │ ThemeProvider (主题状态,可选) │ │ └─ theme (主题配置) │ │ │ │ Router (路由状态) │ │ └─ currentPath (当前路径) │ └─────────────────────────────────────────┘ ``` ## 2. Context实现 ### 2.1 AuthContext(认证上下文) ```javascript // contexts/AuthContext.jsx import { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { authAPI } from '../services/auth'; import { message } from 'antd'; const AuthContext = createContext(null); /** * 认证上下文Provider */ export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [token, setToken] = useState(null); const [loading, setLoading] = useState(true); const [authState, setAuthState] = useState({ isAuthenticated: false, isLoading: true, }); /** * 初始化认证状态 */ useEffect(() => { checkAuth(); }, []); /** * 检查登录状态 */ const checkAuth = async () => { try { const savedToken = localStorage.getItem('token'); const savedUser = localStorage.getItem('user'); if (savedToken && savedUser) { // 验证Token有效性 const currentUser = await authAPI.getCurrentUser(); setToken(savedToken); setUser(currentUser); setAuthState({ isAuthenticated: true, isLoading: false, }); } else { clearAuth(); } } catch (error) { console.error('Auth check failed:', error); clearAuth(); } finally { setLoading(false); } }; /** * 登录 */ const login = async (username, password) => { try { const response = await authAPI.login(username, password); const { token: newToken, user: userData } = response; // 保存到localStorage localStorage.setItem('token', newToken); localStorage.setItem('user', JSON.stringify(userData)); // 更新状态 setToken(newToken); setUser(userData); setAuthState({ isAuthenticated: true, isLoading: false, }); return userData; } catch (error) { console.error('Login failed:', error); throw error; } }; /** * 登出 */ const logout = useCallback(async () => { try { await authAPI.logout(); } catch (error) { console.error('Logout failed:', error); } finally { clearAuth(); } }, []); /** * 清除认证信息 */ const clearAuth = useCallback(() => { localStorage.removeItem('token'); localStorage.removeItem('user'); setToken(null); setUser(null); setAuthState({ isAuthenticated: false, isLoading: false, }); }, []); /** * 更新用户信息 */ const updateUser = useCallback((userData) => { setUser((prev) => ({ ...prev, ...userData })); localStorage.setItem('user', JSON.stringify({ ...user, ...userData })); }, [user]); /** * 检查权限 */ const hasRole = useCallback( (roles) => { if (!user) return false; if (Array.isArray(roles)) { return roles.includes(user.role); } return user.role === roles; }, [user] ); /** * 检查是否可以编辑 */ const canEdit = useCallback( (createdBy) => { if (!user) return false; if (user.role === 'admin') return true; if (user.role === 'market' && createdBy === user.id) return true; return false; }, [user] ); const value = { user, token, loading, authState, login, logout, checkAuth, updateUser, hasRole, canEdit, isAuthenticated: authState.isAuthenticated, }; return ( {children} ); }; /** * 使用AuthContext的Hook */ export const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; }; export default AuthContext; ``` ### 2.2 ThemeContext(主题上下文,可选) ```javascript // contexts/ThemeContext.jsx import { createContext, useContext, useState, useCallback, useEffect } from 'react'; const ThemeContext = createContext(null); export const ThemeProvider = ({ children }) => { const [theme, setTheme] = useState('light'); // 从localStorage加载主题 useEffect(() => { const savedTheme = localStorage.getItem('theme'); if (savedTheme) { setTheme(savedTheme); document.documentElement.setAttribute('data-theme', savedTheme); } }, []); /** * 切换主题 */ const toggleTheme = useCallback(() => { setTheme((prev) => { const newTheme = prev === 'light' ? 'dark' : 'light'; localStorage.setItem('theme', newTheme); document.documentElement.setAttribute('data-theme', newTheme); return newTheme; }); }, []); /** * 设置主题 */ const setThemeMode = useCallback((mode) => { setTheme(mode); localStorage.setItem('theme', mode); document.documentElement.setAttribute('data-theme', mode); }, []); const value = { theme, toggleTheme, setThemeMode, }; return ( {children} ); }; export const useTheme = () => { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; }; export default ThemeContext; ``` ## 3. 自定义Hooks ### 3.1 useAuth Hook ```javascript // hooks/useAuth.js import { useAuth as useAuthContext } from '../contexts/AuthContext'; export const useAuth = () => { return useAuthContext(); }; ``` ### 3.2 usePermission Hook ```javascript // hooks/usePermission.js import { useAuth } from './useAuth'; /** * 权限检查Hook */ export const usePermission = () => { const { user, hasRole, canEdit } = useAuth(); /** * 检查是否是管理员 */ const isAdmin = () => { return user?.role === 'admin'; }; /** * 检查是否是市场部 */ const isMarket = () => { return user?.role === 'market'; }; /** * 检查是否是其他部门 */ const isOther = () => { return user?.role === 'other'; }; /** * 检查是否有指定权限 */ const hasPermission = (permission) => { // 可以扩展更复杂的权限系统 const permissions = { admin: ['create_user', 'edit_user', 'delete_user', 'create_project', 'edit_project', 'delete_project'], market: ['create_project', 'edit_own_project', 'delete_own_project'], other: ['edit_project_cost', 'edit_project_finance', 'edit_project_progress'], }; return permissions[user?.role]?.includes(permission) || false; }; return { isAdmin, isMarket, isOther, hasRole, canEdit, hasPermission, user, }; }; ``` ### 3.3 useModal Hook ```javascript // hooks/useModal.js import { useState, useCallback } from 'react'; /** * 弹窗管理Hook */ export const useModal = () => { const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const open = useCallback((modalData) => { setData(modalData); setVisible(true); }, []); const close = useCallback(() => { setVisible(false); setData(null); setLoading(false); }, []); const toggle = useCallback(() => { setVisible((prev) => !prev); }, []); const setLoadingState = useCallback((state) => { setLoading(state); }, []); return { visible, loading, data, open, close, toggle, setLoading: setLoadingState, }; }; ``` ### 3.4 useTable Hook ```javascript // hooks/useTable.js import { useState, useCallback } from 'react'; /** * 表格管理Hook */ export const useTable = (initialState = {}) => { const { pagination: initialPagination = { current: 1, pageSize: 10 }, filters: initialFilters = {}, sorter: initialSorter = {}, selectedRowKeys: initialSelectedRowKeys = [], } = initialState; const [pagination, setPagination] = useState(initialPagination); const [filters, setFilters] = useState(initialFilters); const [sorter, setSorter] = useState(initialSorter); const [selectedRowKeys, setSelectedRowKeys] = useState(initialSelectedRowKeys); const handleTableChange = useCallback((newPagination, newFilters, newSorter) => { setPagination(newPagination); setFilters(newFilters); setSorter({ field: newSorter.field, order: newSorter.order, }); }, []); const resetTable = useCallback(() => { setPagination(initialPagination); setFilters(initialFilters); setSorter(initialSorter); setSelectedRowKeys(initialSelectedRowKeys); }, [initialPagination, initialFilters, initialSorter, initialSelectedRowKeys]); return { pagination, filters, sorter, selectedRowKeys, setSelectedRowKeys, handleTableChange, resetTable, }; }; ``` ### 3.5 useForm Hook ```javascript // hooks/useForm.js import { useState, useCallback } from 'react'; /** * 表单管理Hook */ export const useForm = (initialValues = {}) => { const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const [touched, setTouched] = useState({}); const [submitting, setSubmitting] = useState(false); /** * 更新表单值 */ const handleChange = useCallback((field, value) => { setValues((prev) => ({ ...prev, [field]: value, })); // 清除该字段的错误 if (errors[field]) { setErrors((prev) => ({ ...prev, [field]: null, })); } }, [errors]); /** * 批量更新表单值 */ const setFieldsValue = useCallback((newValues) => { setValues((prev) => ({ ...prev, ...newValues, })); }, []); /** * 标记字段为已触摸 */ const handleBlur = useCallback((field) => { setTouched((prev) => ({ ...prev, [field]: true, })); }, []); /** * 设置错误 */ const setFieldError = useCallback((field, error) => { setErrors((prev) => ({ ...prev, [field]: error, })); }, []); /** * 批量设置错误 */ const setErrorsAll = useCallback((newErrors) => { setErrors(newErrors); }, []); /** * 验证表单 */ const validate = useCallback((validationRules) => { const newErrors = {}; let isValid = true; Object.keys(validationRules).forEach((field) => { const rules = validationRules[field]; const value = values[field]; for (const rule of rules) { let error = null; if (rule.required && !value) { error = rule.message || `${field} is required`; } else if (rule.pattern && !rule.pattern.test(value)) { error = rule.message || `${field} is invalid`; } else if (rule.validator && !rule.validator(value)) { error = rule.message || `${field} is invalid`; } if (error) { newErrors[field] = error; isValid = false; break; } } }); setErrors(newErrors); return isValid; }, [values]); /** * 重置表单 */ const resetForm = useCallback(() => { setValues(initialValues); setErrors({}); setTouched({}); setSubmitting(false); }, [initialValues]); /** * 提交表单 */ const handleSubmit = useCallback(async (onSubmit, validationRules) => { if (validationRules && !validate(validationRules)) { return false; } setSubmitting(true); try { await onSubmit(values); return true; } catch (error) { console.error('Form submit error:', error); return false; } finally { setSubmitting(false); } }, [values, validate]); return { values, errors, touched, submitting, handleChange, setFieldsValue, handleBlur, setFieldError, setErrorsAll, validate, resetForm, handleSubmit, }; }; ``` ### 3.6 useDebounce Hook ```javascript // hooks/useDebounce.js import { useState, useEffect } from 'react'; /** * 防抖Hook */ export const useDebounce = (value, delay = 300) => { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(timer); }; }, [value, delay]); return debouncedValue; }; ``` ### 3.7 useLocalStorage Hook ```javascript // hooks/useLocalStorage.js import { useState, useEffect, useCallback } from 'react'; /** * LocalStorage Hook */ export const useLocalStorage = (key, initialValue) => { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(`Error reading localStorage key "${key}":`, error); return initialValue; } }); const setValue = useCallback( (value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(`Error setting localStorage key "${key}":`, error); } }, [key, storedValue] ); const removeValue = useCallback(() => { try { window.localStorage.removeItem(key); setStoredValue(initialValue); } catch (error) { console.error(`Error removing localStorage key "${key}":`, error); } }, [key, initialValue]); return [storedValue, setValue, removeValue]; }; ``` ## 4. 状态管理最佳实践 ### 4.1 状态分类 ```javascript // 1. 本地状态 - 使用useState const [value, setValue] = useState(''); // 2. 上下文状态 - 使用Context const { user, login, logout } = useAuth(); // 3. URL状态 - 使用useSearchParams const [searchParams, setSearchParams] = useSearchParams(); // 4. 表单状态 - 使用Form Hook const [form] = Form.useForm(); // 5. 列表状态 - 使用自定义Hook const { list, loading, pagination } = useList(projectAPI.getList); ``` ### 4.2 状态提升 **错误示例:** ```jsx // 父组件 const Parent = () => { const [value, setValue] = useState(''); return ; }; // 子组件 const Child = ({ value, onChange }) => { return onChange(e.target.value)} />; }; ``` **正确示例:** ```jsx // 状态留在子组件 const Child = () => { const [value, setValue] = useState(''); return setValue(e.target.value)} />; }; ``` ### 4.3 避免不必要的重渲染 ```jsx // 使用React.memo const ExpensiveComponent = React.memo(({ data }) => { return
{/* 渲染逻辑 */}
; }); // 使用useMemo缓存计算结果 const expensiveValue = useMemo(() => { return computeExpensiveValue(data); }, [data]); // 使用useCallback缓存函数 const handleClick = useCallback(() => { doSomething(a, b); }, [a, b]); ``` ## 5. 状态持久化 ### 5.1 本地存储策略 ```javascript // 需要持久化的状态 const persistentState = { user: 'localStorage', // 用户信息 token: 'localStorage', // 认证令牌 theme: 'localStorage', // 主题设置 language: 'localStorage', // 语言设置 }; // 不需要持久化的状态 const transientState = { loading: false, // 加载状态 error: null, // 错误信息 modalVisible: false, // 弹窗状态 formData: {}, // 表单数据 }; ``` ### 5.2 状态恢复 ```javascript // 在应用启动时恢复状态 useEffect(() => { const savedTheme = localStorage.getItem('theme'); if (savedTheme) { setTheme(savedTheme); } const savedUser = localStorage.getItem('user'); if (savedUser) { setUser(JSON.parse(savedUser)); } }, []); ``` ## 6. 调试工具 ### 6.1 React DevTools ```javascript // 安装React DevTools浏览器扩展 // 在开发环境中,可以使用React DevTools查看组件树和状态 ``` ### 6.2 自定义状态日志 ```javascript // 开发环境下的状态日志 if (import.meta.env.DEV) { useEffect(() => { console.log('Auth state changed:', { user, token, authState }); }, [user, token, authState]); } ``` ### 6.3 状态变化监听 ```javascript // 监听特定状态的变化 useEffect(() => { if (user) { console.log('User logged in:', user); } else { console.log('User logged out'); } }, [user]); ``` ## 7. 性能优化 ### 7.1 减少Context更新 ```javascript // 错误示例:频繁更新Context const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [timestamp, setTimestamp] = useState(Date.now()); // 这个timestamp变化会导致所有使用AuthContext的组件重渲染 useEffect(() => { setInterval(() => { setTimestamp(Date.now()); }, 1000); }, []); return {children}; }; // 正确示例:分离频繁更新的状态 const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [timestamp, setTimestamp] = useState(Date.now()); useEffect(() => { setInterval(() => { setTimestamp(Date.now()); }, 1000); }, []); return ( <> {children} {/* 不渲染 */} ); }; ``` ### 7.2 使用useReducer ```javascript // 对于复杂的状态逻辑,使用useReducer const initialState = { user: null, token: null, loading: true, error: null, }; function authReducer(state, action) { switch (action.type) { case 'LOGIN_SUCCESS': return { ...state, user: action.payload.user, token: action.payload.token, loading: false, }; case 'LOGOUT': return { ...state, user: null, token: null, }; default: return state; } } const AuthProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, initialState); return ( {children} ); }; ``` ## 8. 未来扩展 ### 8.1 迁移到Redux 如果项目变得复杂,可以考虑迁移到Redux Toolkit: ```javascript // slices/authSlice.js import { createSlice } from '@reduxjs/toolkit'; const authSlice = createSlice({ name: 'auth', initialState: { user: null, token: null, loading: true, }, reducers: { setCredentials: (state, action) => { state.user = action.payload.user; state.token = action.payload.token; state.loading = false; }, logout: (state) => { state.user = null; state.token = null; }, }, }); export const { setCredentials, logout } = authSlice.actions; export default authSlice.reducer; ``` ### 8.2 迁移到Zustand Zustand是一个更轻量级的替代方案: ```javascript // store/authStore.js import { create } from 'zustand'; export const useAuthStore = create((set) => ({ user: null, token: null, loading: true, login: (user, token) => set({ user, token, loading: false }), logout: () => set({ user: null, token: null }), })); ``` --- **文档维护**: 前端程序员 **文档类型**: 状态管理设计文档 **最后更新**: 2026-01-25