save code
This commit is contained in:
@@ -0,0 +1,958 @@
|
||||
# 状态管理设计文档
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user