959 lines
21 KiB
Markdown
959 lines
21 KiB
Markdown
# 状态管理设计文档
|
||
|
||
## 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 (
|
||
<AuthContext.Provider value={value}>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
);
|
||
};
|
||
|
||
/**
|
||
* 使用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 (
|
||
<ThemeContext.Provider value={value}>
|
||
{children}
|
||
</ThemeContext.Provider>
|
||
);
|
||
};
|
||
|
||
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 <Child value={value} onChange={setValue} />;
|
||
};
|
||
|
||
// 子组件
|
||
const Child = ({ value, onChange }) => {
|
||
return <Input value={value} onChange={(e) => onChange(e.target.value)} />;
|
||
};
|
||
```
|
||
|
||
**正确示例:**
|
||
```jsx
|
||
// 状态留在子组件
|
||
const Child = () => {
|
||
const [value, setValue] = useState('');
|
||
return <Input value={value} onChange={(e) => setValue(e.target.value)} />;
|
||
};
|
||
```
|
||
|
||
### 4.3 避免不必要的重渲染
|
||
|
||
```jsx
|
||
// 使用React.memo
|
||
const ExpensiveComponent = React.memo(({ data }) => {
|
||
return <div>{/* 渲染逻辑 */}</div>;
|
||
});
|
||
|
||
// 使用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 <AuthContext.Provider value={{ user, timestamp }}>{children}</AuthContext.Provider>;
|
||
};
|
||
|
||
// 正确示例:分离频繁更新的状态
|
||
const AuthProvider = ({ children }) => {
|
||
const [user, setUser] = useState(null);
|
||
const [timestamp, setTimestamp] = useState(Date.now());
|
||
|
||
useEffect(() => {
|
||
setInterval(() => {
|
||
setTimestamp(Date.now());
|
||
}, 1000);
|
||
}, []);
|
||
|
||
return (
|
||
<>
|
||
<AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
|
||
<TimestampContext.Provider value={{ timestamp}}>{/* 不渲染 */}</TimestampContext.Provider>
|
||
</>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 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 (
|
||
<AuthContext.Provider value={{ state, dispatch }}>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
);
|
||
};
|
||
```
|
||
|
||
## 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
|