# 状态管理设计文档
## 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