React 状态管理演进:从 useState 到 Redux Toolkit
React 状态管理演进:从 useState 到 Redux Toolkit
一、Introduction
React 状态管理的复杂度取决于应用的规模。对于简单的单页应用,useState + useContext 的组合完全够用;但当应用扩展到几十个组件、多个数据来源、复杂的状态流转逻辑时,就需要更系统化的状态管理方案。
本文梳理 React 状态管理的演进路径:从基础的 useState,到抽象逻辑的 useReducer,再到 Context 共享状态,最终到 Redux Toolkit——每种方案的适用场景和取舍,帮助你在不同阶段选择合适的工具。
---
二、useState + useReducer:组件内部状态
#### 2.1 简单状态用 useState
单个组件内部的状态,直接用 useState。这是 React 状态管理的起点,也是大多数场景的答案。
import React, { useState } from 'react';
function LikeButton() {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(0);
return (
<button
onClick={() => {
setLiked(!liked);
setCount(c => liked ? c - 1 : c + 1);
}}
style={{
color: liked ? 'red' : 'gray',
fontSize: 18
}}
>
{liked ? '❤️' : '🤍'} {count}
</button>
);
}
#### 2.2 复杂状态用 useReducer
当组件内部有多个相关联的状态字段,且状态的更新逻辑复杂时,用 useReducer 集中管理:
import React, { useReducer } from 'react';
// 状态和 action 的定义清晰且集中
const initialState = {
filter: 'all', // 'all' | 'active' | 'completed'
todos: [
{ id: 1, text: '学习 React', completed: false },
{ id: 2, text: '学习 TypeScript', completed: true },
{ id: 3, text: '完成项目', completed: false },
]
};
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload,
completed: false
}
]
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'DELETE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
case 'CLEAR_COMPLETED':
return {
...state,
todos: state.todos.filter(todo => !todo.completed)
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const [inputText, setInputText] = React.useState('');
const filteredTodos = state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
});
const activeCount = state.todos.filter(t => !t.completed).length;
return (
<div style={{ maxWidth: 400 }}>
<h2>待办事项({activeCount} 项待完成)</h2>
<form
onSubmit={e => {
e.preventDefault();
if (!inputText.trim()) return;
dispatch({ type: 'ADD_TODO', payload: inputText });
setInputText('');
}}
>
<input
value={inputText}
onChange={e => setInputText(e.target.value)}
placeholder="添加新待办..."
style={{ width: '70%', padding: 8 }}
/>
<button type="submit" style={{ padding: 8 }}>添加</button>
</form>
<ul style={{ listStyle: 'none', padding: 0 }}>
{filteredTodos.map(todo => (
<li
key={todo.id}
style={{
padding: '8px 0',
borderBottom: '1px solid #eee',
display: 'flex',
alignItems: 'center'
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
style={{ marginRight: 8 }}
/>
<span
style={{
flex: 1,
textDecoration: todo.completed ? 'line-through' : 'none',
color: todo.completed ? '#999' : '#333'
}}
>
{todo.text}
</span>
<button
onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}
style={{ color: 'red', border: 'none', background: 'none', cursor: 'pointer' }}
>
×
</button>
</li>
))}
</ul>
<div style={{ marginTop: 12 }}>
<span>筛选:</span>
{['all', 'active', 'completed'].map(f => (
<button
key={f}
onClick={() => dispatch({ type: 'SET_FILTER', payload: f })}
disabled={state.filter === f}
style={{ marginLeft: 4 }}
>
{f === 'all' ? '全部' : f === 'active' ? '进行中' : '已完成'}
</button>
))}
<button
onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}
style={{ marginLeft: 12, color: 'red' }}
>
清除已完成
</button>
</div>
</div>
);
}
export default TodoApp;
---
三、Redux Toolkit:企业级状态管理
#### 3.1 为什么需要 Redux Toolkit?
原生 Redux(createStore + reducer + dispatch)的样板代码太多。Redux Toolkit(RTK)通过约定优于配置的方式,大幅简化了 Redux 的使用,同时保留了 Redux 的核心优势:可预测的状态、强大的 DevTools、丰富的中间件生态。
import React from 'react';
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
// ==================== Redux Slice 定义 ====================
// 创建异步 thunk:发起 API 请求
const fetchPosts = createAsyncThunk(
'posts/fetchPosts',
async (userId) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?userId=${userId}`
);
if (!response.ok) throw new Error('网络错误');
return response.json();
}
);
// 创建 slice:包含状态初始值和 reducer
const postsSlice = createSlice({
name: 'posts',
initialState: {
items: [],
loading: false,
error: null,
selectedPostId: null
},
reducers: {
selectPost: (state, action) => {
state.selectedPostId = action.payload;
},
clearPosts: (state) => {
state.items = [];
state.selectedPostId = null;
}
},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
const { selectPost, clearPosts } = postsSlice.actions;
// ==================== Store 配置 ====================
const store = configureStore({
reducer: {
posts: postsSlice.reducer
}
});
// ==================== React 组件 ====================
function ReduxPostsApp() {
const dispatch = useDispatch();
const { items, loading, error, selectedPostId } = useSelector(state => state.posts);
const selectedPost = items.find(p => p.id === selectedPostId);
return (
<Provider store={store}>
<div style={{ display: 'flex', gap: 20 }}>
<div style={{ flex: 1 }}>
<h3>文章列表(Redux Toolkit)</h3>
<div style={{ marginBottom: 12 }}>
{[1, 2, 3].map(userId => (
<button
key={userId}
onClick={() => dispatch(fetchPosts(userId))}
style={{ marginRight: 4 }}
>
加载用户 {userId} 的文章
</button>
))}
<button
onClick={() => dispatch(clearPosts())}
style={{ color: 'red' }}
>
清空
</button>
</div>
{loading && <p>加载中...</p>}
{error && <p style={{ color: 'red' }}>错误:{error}</p>}
{!loading && items.length === 0 && (
<p style={{ color: '#888' }}>点击按钮加载文章</p>
)}
<ul>
{items.slice(0, 10).map(post => (
<li
key={post.id}
onClick={() => dispatch(selectPost(post.id))}
style={{
padding: 8,
cursor: 'pointer',
background: selectedPostId === post.id ? '#e3f2fd' : 'transparent',
border: '1px solid #eee',
marginBottom: 4
}}
>
<strong>{post.id}.</strong> {post.title}
</li>
))}
</ul>
</div>
{selectedPost && (
<div style={{ flex: 1, borderLeft: '1px solid #ddd', paddingLeft: 20 }}>
<h3>文章详情(Redux 选中状态)</h3>
<p style={{ color: '#888', fontSize: 12 }}>
当前选中 ID: {selectedPost.id}
</p>
<h4>{selectedPost.title}</h4>
<p>{selectedPost.body}</p>
</div>
)}
</div>
</Provider>
);
}
export default ReduxPostsApp;
---
四、运行效果说明
TodoApp:使用 useReducer + 清晰的状态和 action 定义。添加/删除/切换完成/筛选等功能全部正常工作,状态流转可预测且易于调试。
ReduxPostsApp:点击"加载用户 X 的文章",Redux 发起异步 thunk,loading 状态显示"加载中",完成后列表渲染文章;点击列表项,通过 Redux action 更新 selectedPostId,右侧详情区显示文章内容;Redux DevTools 可以完整看到每次状态变化的快照和 action 历史。
---
五、常见问题(FAQ)
#### Q1:useState、useReducer、Redux 分别用在什么场景?
- <strong>useState</strong>:单组件内部的状态,不跨组件共享
- <strong>useReducer</strong>:单组件内复杂状态,或用 Context 共享的全局状态
- <strong>Redux</strong>:跨多个页面/路由的复杂全局状态,需要强大的调试工具(时间旅行)、中间件生态(saga/thunk)、或团队协作时
不要过度设计——如果你只有一个简单的全局状态(如当前主题),用 Context 就够了,不需要引入 Redux 的复杂度。
#### Q2:Redux Toolkit 和原生 Redux 的区别是什么?
Redux Toolkit 是 Redux 的官方推荐方式,它在原生 Redux 之上做了大量简化:
- 内置 <code>createSlice</code>,用对象语法定义 reducers(比 switch-case 简洁得多)
- 内置 <code>createAsyncThunk</code>,处理异步逻辑无需安装额外中间件
- 默认启用 Redux DevTools,无需额外配置
- 不可变性自动处理(Immer),可以直接修改 state 树
#### Q3:Redux 的状态更新是同步还是异步的?
默认是同步的。原生 Redux 的 dispatch 是同步的,只有通过中间件(如 redux-thunk 或 redux-saga)才能处理异步逻辑。Redux Toolkit 的 createAsyncThunk 内部也使用了 thunk 中间件来支持异步 dispatch。
---
六、延伸阅读
- [React Hooks 完全指南:useState 与 useEffect 核心用法]()
- [React Context API 状态管理深度理解]()
- [React 自定义 Hooks 实战:逻辑复用与模块化]()
- [React useCallback 与 useMemo 性能优化完全指南]()
---