随着应用规模的增长,组件间的状态共享变得越来越复杂。本篇将介绍 React 中的状态管理方案,以及如何在实际项目中选择合适的状态管理策略。
状态管理的选择策略
何时需要状态管理库?
- 组件间状态共享:多个组件需要访问相同的数据
- 复杂的状态逻辑:状态更新逻辑复杂,需要统一管理
- 性能优化:避免不必要的重新渲染
- 开发体验:更好的调试工具和开发体验
选择标准
- 项目规模:小项目用 Context,大项目用专业库
- 团队熟悉度:选择团队熟悉的方案
- 性能要求:对性能要求高的项目选择轻量级方案
- 调试需求:需要强大调试工具的项目选择 Redux
Context API 的局限性
虽然 Context API 可以解决状态共享问题,但它有一些局限性:
// Context API 的问题示例
import { createContext, useContext, useState } from 'react';
const AppContext = createContext(null);
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// 问题:所有状态变化都会导致所有消费者重新渲染
const value = { user, setUser, theme, setTheme, notifications, setNotifications };
return (
<AppContext.Provider value={value}>
<Header />
<Main />
<Sidebar />
</AppContext.Provider>
);
}
function Header() {
const { theme } = useContext(AppContext);
// 当 user 或 notifications 变化时,Header 也会重新渲染
return <header>Theme: {theme}</header>;
}
主要问题:
- 性能问题:任何状态变化都会导致所有消费者重新渲染
- 缺乏中间件:无法处理异步操作、日志记录等
- 调试困难:缺乏专业的调试工具
Zustand:轻量级状态管理
Zustand 是一个轻量级的状态管理库,API 简单,学习成本低。
基本使用
import { create } from 'zustand';
// 定义 store
interface UserStore {
user: User | null;
login: (user: User) => void;
logout: () => void;
}
const useUserStore = create<UserStore>((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null }),
}));
// 在组件中使用
function UserProfile() {
const { user, logout } = useUserStore();
if (!user) return <div>请先登录</div>;
return (
<div>
<h1>欢迎,{user.name}</h1>
<button onClick={logout}>退出登录</button>
</div>
);
}
复杂状态管理
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface TodoStore {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
removeTodo: (id: number) => void;
setFilter: (filter: 'all' | 'active' | 'completed') => void;
getFilteredTodos: () => Todo[];
}
const useTodoStore = create<TodoStore>()(
devtools(
(set, get) => ({
todos: [],
filter: 'all',
addTodo: (text) => set((state) => ({
todos: [...state.todos, { id: Date.now(), text, completed: false }]
})),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
setFilter: (filter) => set({ filter }),
getFilteredTodos: () => {
const { todos, filter } = get();
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
},
}),
{ name: 'todo-store' }
)
);
// 使用示例
function TodoApp() {
const {
todos,
filter,
addTodo,
toggleTodo,
removeTodo,
setFilter,
getFilteredTodos
} = useTodoStore();
const filteredTodos = getFilteredTodos();
return (
<div>
<div>
<input
type="text"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addTodo(e.currentTarget.value);
e.currentTarget.value = '';
}
}}
placeholder="添加新任务..."
/>
</div>
<div>
<button onClick={() => setFilter('all')}>全部</button>
<button onClick={() => setFilter('active')}>进行中</button>
<button onClick={() => setFilter('completed')}>已完成</button>
</div>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
Redux Toolkit:企业级状态管理
Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 的使用。
基本概念
import { createSlice, configureStore } from '@reduxjs/toolkit';
// 定义 slice
const userSlice = createSlice({
name: 'user',
initialState: {
user: null,
loading: false,
error: null,
},
reducers: {
setUser: (state, action) => {
state.user = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
},
});
// 异步 action
export const loginUser = (credentials) => async (dispatch) => {
dispatch(setLoading(true));
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
});
const user = await response.json();
dispatch(setUser(user));
} catch (error) {
dispatch(setError(error.message));
} finally {
dispatch(setLoading(false));
}
};
// 配置 store
const store = configureStore({
reducer: {
user: userSlice.reducer,
},
});
// 在组件中使用
import { useSelector, useDispatch } from 'react-redux';
function LoginForm() {
const dispatch = useDispatch();
const { loading, error } = useSelector((state) => state.user);
const handleSubmit = (e) => {
e.preventDefault();
const credentials = {
email: e.target.email.value,
password: e.target.password.value,
};
dispatch(loginUser(credentials));
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input name="email" type="email" placeholder="邮箱" />
<input name="password" type="password" placeholder="密码" />
<button type="submit" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
);
}
状态管理方案对比
特性 | Context API | Zustand | Redux Toolkit |
---|---|---|---|
学习成本 | 低 | 低 | 中等 |
Bundle 大小 | 0KB | 2KB | 15KB |
性能 | 一般 | 好 | 好 |
调试工具 | 无 | 基础 | 强大 |
中间件支持 | 无 | 有 | 丰富 |
适用场景 | 简单应用 | 中小型应用 | 大型应用 |
实际项目中的选择策略
小型项目(< 10 个页面)
- 推荐:Context API + useState
- 原因:简单直接,无需额外依赖
中型项目(10-50 个页面)
- 推荐:Zustand
- 原因:API 简单,性能好,调试工具完善
大型项目(> 50 个页面)
- 推荐:Redux Toolkit
- 原因:生态完善,团队协作好,调试工具强大
特殊场景
- 实时数据:考虑 Zustand + WebSocket
- 复杂表单:考虑 React Hook Form + Zustand
- 服务端状态:考虑 TanStack Query + Zustand
最佳实践
1. 状态设计原则
// 好的状态设计
interface AppState {
// 用户相关
user: User | null;
userPreferences: UserPreferences;
// 应用状态
theme: 'light' | 'dark';
language: string;
// 业务数据
todos: Todo[];
notifications: Notification[];
}
// 避免的状态设计
interface BadAppState {
// 不要存储派生状态
completedTodosCount: number; // 应该通过计算得出
filteredTodos: Todo[]; // 应该通过选择器得出
}
2. 状态分离
// 按功能分离状态
const useUserStore = create()(/* 用户相关状态 */);
const useTodoStore = create()(/* 待办事项状态 */);
const useUISStore = create()(/* UI 状态 */);
// 避免单一大型 store
const useAppStore = create()(/* 所有状态混在一起 */);
3. 性能优化
// 使用选择器避免不必要的重新渲染
const useUserStore = create((set, get) => ({
user: null,
userProfile: null,
userSettings: null,
}));
// 好的做法:只订阅需要的状态
function UserProfile() {
const userProfile = useUserStore((state) => state.userProfile);
// 只有 userProfile 变化时才重新渲染
}
// 避免的做法:订阅整个 store
function UserProfile() {
const { userProfile } = useUserStore();
// 任何状态变化都会重新渲染
}
总结
- Context API:适合简单的状态共享,但要注意性能问题
- Zustand:轻量级选择,API 简单,性能好
- Redux Toolkit:企业级选择,功能强大,生态完善
- 选择策略:根据项目规模和团队需求选择合适的方案
- 最佳实践:合理设计状态结构,注意性能优化
掌握这些状态管理方案,你就能构建出可扩展、可维护的 React 应用。