Skip to main content

React状态管理完全指南

状态管理是React应用开发的核心挑战之一。随着应用规模的增长,如何高效、可维护地管理应用状态变得至关重要。本指南将深入探讨各种状态管理方案,帮助你选择最适合项目需求的解决方案。

核心价值

状态管理 = 数据流控制 + 状态共享 + 性能优化 + 开发体验

  • 🎯 数据流控制:单向数据流,可预测的状态变化
  • 🔄 状态共享:跨组件状态共享,避免prop drilling
  • 性能优化:精确更新,避免不必要的重新渲染
  • 🛠️ 开发体验:调试工具,时间旅行,状态持久化
  • 📊 可维护性:清晰的状态结构,易于测试和维护
  • 🎨 灵活性:适应不同规模和复杂度的应用需求

1. 状态管理方案对比

1.1 方案选择指南

选择合适的状态管理方案需要考虑应用规模、团队经验、性能要求等多个因素。

状态管理方案对比表

方案学习曲线包大小性能生态系统适用场景TypeScript支持
useState + useContext0KB中等React内置小型应用⭐⭐⭐
Redux Toolkit中等47KB丰富中大型应用⭐⭐⭐⭐⭐
Zustand8KB中等中小型应用⭐⭐⭐⭐
Jotai中等13KB新兴原子化状态⭐⭐⭐⭐⭐
Recoil中等79KBFacebook复杂状态图⭐⭐⭐⭐
MobX16KB成熟面向对象⭐⭐⭐⭐
Valtio9KB新兴代理状态⭐⭐⭐⭐

React内置状态管理

对于小到中型应用,React的内置状态管理通常已经足够。

React内置状态管理最佳实践
typescript
1import React, { createContext, useContext, useReducer, useState, useCallback, useMemo } from 'react';
2
3// 1. 简单状态管理 - useState + useContext
4interface AppState {
5 user: User | null;
6 theme: 'light' | 'dark';
7 language: 'zh' | 'en';
8 notifications: Notification[];
9}
10
11interface User {
12 id: number;
13 name: string;
14 email: string;
15 avatar?: string;
16}
17
18interface Notification {
19 id: string;
20 type: 'success' | 'error' | 'warning' | 'info';
21 message: string;
22 timestamp: number;
23}
24
25// Context定义
26const AppStateContext = createContext<{
27 state: AppState;
28 actions: {
29 setUser: (user: User | null) => void;
30 toggleTheme: () => void;
31 setLanguage: (language: 'zh' | 'en') => void;
32 addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => void;
33 removeNotification: (id: string) => void;
34 clearNotifications: () => void;
35 };
36} | undefined>(undefined);
37
38// Provider组件
39export const AppStateProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
40 const [state, setState] = useState<AppState>({
41 user: null,
42 theme: 'light',
43 language: 'zh',
44 notifications: []
45 });
46
47 // 优化的action creators
48 const actions = useMemo(() => ({
49 setUser: (user: User | null) => {
50 setState(prev => ({ ...prev, user }));
51 },
52
53 toggleTheme: () => {
54 setState(prev => ({
55 ...prev,
56 theme: prev.theme === 'light' ? 'dark' : 'light'
57 }));
58 },
59
60 setLanguage: (language: 'zh' | 'en') => {
61 setState(prev => ({ ...prev, language }));
62 },
63
64 addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => {
65 const newNotification: Notification = {
66 ...notification,
67 id: Math.random().toString(36).substr(2, 9),
68 timestamp: Date.now()
69 };
70
71 setState(prev => ({
72 ...prev,
73 notifications: [...prev.notifications, newNotification]
74 }));
75
76 // 自动移除通知
77 setTimeout(() => {
78 setState(prev => ({
79 ...prev,
80 notifications: prev.notifications.filter(n => n.id !== newNotification.id)
81 }));
82 }, 5000);
83 },
84
85 removeNotification: (id: string) => {
86 setState(prev => ({
87 ...prev,
88 notifications: prev.notifications.filter(n => n.id !== id)
89 }));
90 },
91
92 clearNotifications: () => {
93 setState(prev => ({ ...prev, notifications: [] }));
94 }
95 }), []);
96
97 const value = useMemo(() => ({ state, actions }), [state, actions]);
98
99 return (
100 <AppStateContext.Provider value={value}>
101 {children}
102 </AppStateContext.Provider>
103 );
104};
105
106// Hook
107export const useAppState = () => {
108 const context = useContext(AppStateContext);
109 if (context === undefined) {
110 throw new Error('useAppState must be used within an AppStateProvider');
111 }
112 return context;
113};
114
115// 2. 复杂状态管理 - useReducer
116interface TodoState {
117 todos: Todo[];
118 filter: 'all' | 'active' | 'completed';
119 loading: boolean;
120 error: string | null;
121}
122
123interface Todo {
124 id: string;
125 text: string;
126 completed: boolean;
127 createdAt: number;
128 updatedAt: number;
129}
130
131type TodoAction =
132 | { type: 'ADD_TODO'; payload: { text: string } }
133 | { type: 'TOGGLE_TODO'; payload: { id: string } }
134 | { type: 'DELETE_TODO'; payload: { id: string } }
135 | { type: 'EDIT_TODO'; payload: { id: string; text: string } }
136 | { type: 'SET_FILTER'; payload: { filter: TodoState['filter'] } }
137 | { type: 'SET_LOADING'; payload: { loading: boolean } }
138 | { type: 'SET_ERROR'; payload: { error: string | null } }
139 | { type: 'LOAD_TODOS'; payload: { todos: Todo[] } }
140 | { type: 'CLEAR_COMPLETED' };
141
142const todoReducer = (state: TodoState, action: TodoAction): TodoState => {
143 switch (action.type) {
144 case 'ADD_TODO': {
145 const newTodo: Todo = {
146 id: Math.random().toString(36).substr(2, 9),
147 text: action.payload.text,
148 completed: false,
149 createdAt: Date.now(),
150 updatedAt: Date.now()
151 };
152
153 return {
154 ...state,
155 todos: [...state.todos, newTodo],
156 error: null
157 };
158 }
159
160 case 'TOGGLE_TODO':
161 return {
162 ...state,
163 todos: state.todos.map(todo =>
164 todo.id === action.payload.id
165 ? { ...todo, completed: !todo.completed, updatedAt: Date.now() }
166 : todo
167 )
168 };
169
170 case 'DELETE_TODO':
171 return {
172 ...state,
173 todos: state.todos.filter(todo => todo.id !== action.payload.id)
174 };
175
176 case 'EDIT_TODO':
177 return {
178 ...state,
179 todos: state.todos.map(todo =>
180 todo.id === action.payload.id
181 ? { ...todo, text: action.payload.text, updatedAt: Date.now() }
182 : todo
183 )
184 };
185
186 case 'SET_FILTER':
187 return {
188 ...state,
189 filter: action.payload.filter
190 };
191
192 case 'SET_LOADING':
193 return {
194 ...state,
195 loading: action.payload.loading
196 };
197
198 case 'SET_ERROR':
199 return {
200 ...state,
201 error: action.payload.error,
202 loading: false
203 };
204
205 case 'LOAD_TODOS':
206 return {
207 ...state,
208 todos: action.payload.todos,
209 loading: false,
210 error: null
211 };
212
213 case 'CLEAR_COMPLETED':
214 return {
215 ...state,
216 todos: state.todos.filter(todo => !todo.completed)
217 };
218
219 default:
220 return state;
221 }
222};
223
224// Todo Context
225const TodoContext = createContext<{
226 state: TodoState;
227 dispatch: React.Dispatch<TodoAction>;
228 actions: {
229 addTodo: (text: string) => void;
230 toggleTodo: (id: string) => void;
231 deleteTodo: (id: string) => void;
232 editTodo: (id: string, text: string) => void;
233 setFilter: (filter: TodoState['filter']) => void;
234 clearCompleted: () => void;
235 loadTodos: () => Promise<void>;
236 };
237} | undefined>(undefined);
238
239export const TodoProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
240 const [state, dispatch] = useReducer(todoReducer, {
241 todos: [],
242 filter: 'all',
243 loading: false,
244 error: null
245 });
246
247 // Action creators
248 const actions = useMemo(() => ({
249 addTodo: (text: string) => {
250 dispatch({ type: 'ADD_TODO', payload: { text } });
251 },
252
253 toggleTodo: (id: string) => {
254 dispatch({ type: 'TOGGLE_TODO', payload: { id } });
255 },
256
257 deleteTodo: (id: string) => {
258 dispatch({ type: 'DELETE_TODO', payload: { id } });
259 },
260
261 editTodo: (id: string, text: string) => {
262 dispatch({ type: 'EDIT_TODO', payload: { id, text } });
263 },
264
265 setFilter: (filter: TodoState['filter']) => {
266 dispatch({ type: 'SET_FILTER', payload: { filter } });
267 },
268
269 clearCompleted: () => {
270 dispatch({ type: 'CLEAR_COMPLETED' });
271 },
272
273 loadTodos: async () => {
274 dispatch({ type: 'SET_LOADING', payload: { loading: true } });
275
276 try {
277 // 模拟API调用
278 const response = await fetch('/api/todos');
279 if (!response.ok) {
280 throw new Error('Failed to load todos');
281 }
282
283 const todos = await response.json();
284 dispatch({ type: 'LOAD_TODOS', payload: { todos } });
285 } catch (error) {
286 dispatch({
287 type: 'SET_ERROR',
288 payload: { error: error instanceof Error ? error.message : 'Unknown error' }
289 });
290 }
291 }
292 }), []);
293
294 const value = useMemo(() => ({ state, dispatch, actions }), [state, actions]);
295
296 return (
297 <TodoContext.Provider value={value}>
298 {children}
299 </TodoContext.Provider>
300 );
301};
302
303export const useTodos = () => {
304 const context = useContext(TodoContext);
305 if (context === undefined) {
306 throw new Error('useTodos must be used within a TodoProvider');
307 }
308 return context;
309};
310
311// 使用示例
312const TodoApp: React.FC = () => {
313 const { state, actions } = useTodos();
314 const [newTodoText, setNewTodoText] = useState('');
315
316 // 过滤todos
317 const filteredTodos = useMemo(() => {
318 switch (state.filter) {
319 case 'active':
320 return state.todos.filter(todo => !todo.completed);
321 case 'completed':
322 return state.todos.filter(todo => todo.completed);
323 default:
324 return state.todos;
325 }
326 }, [state.todos, state.filter]);
327
328 const handleAddTodo = useCallback((e: React.FormEvent) => {
329 e.preventDefault();
330 if (newTodoText.trim()) {
331 actions.addTodo(newTodoText.trim());
332 setNewTodoText('');
333 }
334 }, [newTodoText, actions]);
335
336 useEffect(() => {
337 actions.loadTodos();
338 }, [actions]);
339
340 if (state.loading) {
341 return <div className="loading">加载中...</div>;
342 }
343
344 if (state.error) {
345 return <div className="error">错误: {state.error}</div>;
346 }
347
348 return (
349 <div className="todo-app">
350 <h1>待办事项</h1>
351
352 <form onSubmit={handleAddTodo} className="add-todo-form">
353 <input
354 type="text"
355 value={newTodoText}
356 onChange={(e) => setNewTodoText(e.target.value)}
357 placeholder="添加新的待办事项..."
358 className="todo-input"
359 />
360 <button type="submit">添加</button>
361 </form>
362
363 <div className="todo-filters">
364 <button
365 className={state.filter === 'all' ? 'active' : ''}
366 onClick={() => actions.setFilter('all')}
367 >
368 全部
369 </button>
370 <button
371 className={state.filter === 'active' ? 'active' : ''}
372 onClick={() => actions.setFilter('active')}
373 >
374 未完成
375 </button>
376 <button
377 className={state.filter === 'completed' ? 'active' : ''}
378 onClick={() => actions.setFilter('completed')}
379 >
380 已完成
381 </button>
382 </div>
383
384 <ul className="todo-list">
385 {filteredTodos.map(todo => (
386 <TodoItem
387 key={todo.id}
388 todo={todo}
389 onToggle={() => actions.toggleTodo(todo.id)}
390 onDelete={() => actions.deleteTodo(todo.id)}
391 onEdit={(text) => actions.editTodo(todo.id, text)}
392 />
393 ))}
394 </ul>
395
396 {state.todos.some(todo => todo.completed) && (
397 <button onClick={actions.clearCompleted} className="clear-completed">
398 清除已完成
399 </button>
400 )}
401 </div>
402 );
403};

2. 状态设计原则与最佳实践

参与讨论