React
Redux 的原理和工作中常用方式
这篇笔记记录一下我对 Redux 的理解。 我刚开始学 Redux 时,最困惑的是它看起来很绕: 后来写多了以后发现,Redux 不是为了让简单事情变复杂,而是为了让状态变化变得可追踪。 它最核心的思路可以压缩成一句话: Redux 解决什么问题 React 组件里可以直接用 useState。 小项目这样写完全没问题

这篇笔记记录一下我对 Redux 的理解。
我刚开始学 Redux 时,最困惑的是它看起来很绕:
改一个状态,为什么不能直接改?
为什么要 dispatch 一个 action?
为什么还要 reducer?后来写多了以后发现,Redux 不是为了让简单事情变复杂,而是为了让状态变化变得可追踪。
它最核心的思路可以压缩成一句话:
用 action 描述发生了什么,用 reducer 根据旧状态计算新状态。Redux 解决什么问题
React 组件里可以直接用 useState。
小项目这样写完全没问题:
const [count, setCount] = useState(0);但项目变大后,有些状态会被很多地方使用:
- 当前登录用户。
- 权限信息。
- 主题配置。
- 多页面共享的筛选条件。
- 复杂表单数据。
- 购物车。
- 编辑器状态。
如果这些状态分散在各个组件里,问题会变多:
谁改了这个状态?
为什么这个页面也变了?
这个状态到底从哪里来?
多个组件要同步怎么办?Redux 的目标就是把共享状态集中管理,并让每次修改都有明确记录。
Redux 的三个核心概念
最基础的 Redux 有三个概念:
store
action
reducerStore
Store 是保存全局状态的地方。
可以粗略理解成:
const state = store.getState();整个应用需要共享的状态,都可以从这里读。
Action
Action 是一个普通对象,用来描述发生了什么。
例如:
{
type: "counter/increment"
}或者:
{
type: "user/setName",
payload: "小明"
}Action 不负责修改状态。
它只是描述事件。
Reducer
Reducer 根据旧状态和 action,返回新状态。
function counterReducer(state = { value: 0 }, action) {
if (action.type === "counter/increment") {
return {
value: state.value + 1,
};
}
return state;
}Reducer 应该是纯函数。
同样的 state 和 action,应该得到同样的新状态。
一次状态更新的流程
Redux 的数据流是单向的。
UI 触发事件
-> dispatch(action)
-> reducer 计算新 state
-> store 保存新 state
-> UI 根据新 state 重新渲染例如点击按钮:
dispatch({ type: "counter/increment" });Redux 会把 action 交给 reducer:
newState = reducer(oldState, action);然后 React 组件通过 selector 读到新状态。
这个流程虽然比直接 setState 多了几步,但好处是非常清楚:
每次变化都由一个 action 触发。为什么不能直接修改 state
Redux 强调不可变更新。
不要这样写:
state.value += 1;
return state;而是返回新对象:
return {
...state,
value: state.value + 1,
};原因是 React 和 Redux 都需要通过引用变化判断状态是否更新。
如果直接改原对象,外面可能看不出引用变了。
不过现在常用 Redux Toolkit,它内部使用 Immer,可以让我们写出看起来像直接修改的代码:
increment(state) {
state.value += 1;
}Immer 会帮我们生成不可变的新状态。
工作中更常用 Redux Toolkit
现在不太推荐从零手写大量 Redux 模板代码。
更常用的是 Redux Toolkit。
一个 slice 大概长这样:
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
increment(state) {
state.value += 1;
},
add(state, action) {
state.value += action.payload;
},
},
});
export const { increment, add } = counterSlice.actions;
export default counterSlice.reducer;使用时:
const value = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
{value}
</button>
);Redux Toolkit 帮我们减少了很多重复代码:
- 自动生成 action creator。
- 自动处理不可变更新。
- 简化 store 配置。
- 默认集成常用中间件。
异步请求怎么处理
Redux 的 reducer 不能直接写异步逻辑。
因为 reducer 应该是纯函数。
异步请求通常放在 thunk 里。
Redux Toolkit 提供了 createAsyncThunk:
export const fetchUser = createAsyncThunk(
"user/fetchUser",
async (userId: string) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
);slice 里处理不同状态:
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state) => {
state.loading = false;
state.error = "加载失败";
});
}这样请求过程也会变成清晰的状态变化:
pending
fulfilled
rejectedSelector 的作用
组件不一定要直接知道完整 state 结构。
可以用 selector 封装读取逻辑:
export const selectCurrentUser = (state: RootState) => {
return state.user.current;
};组件里:
const user = useSelector(selectCurrentUser);如果读取逻辑复杂,还可以做派生计算。
Selector 的好处是:
- 组件更干净。
- state 结构调整时影响范围更小。
- 复杂计算可以集中管理。
哪些状态适合放 Redux
不是所有状态都要放 Redux。
适合放 Redux 的状态通常有这些特征:
- 多个页面或组件共享。
- 需要跨路由保留。
- 状态变化需要追踪。
- 和业务身份、权限、配置有关。
- 多个操作会影响同一份数据。
不太适合放 Redux 的状态:
- 输入框临时内容。
- 弹窗开关。
- 鼠标悬停。
- 某个组件内部的小状态。
这些用 useState 或自定义 Hook 更简单。
Redux 不是状态越多越好,而是共享状态需要有秩序。
我最后想通的地方
Redux 的核心不是“全局变量仓库”。
如果只是为了全局能读写,随便一个对象也能做到。
Redux 真正强调的是:
状态集中保存
变化通过 action 描述
reducer 负责计算新状态
UI 根据状态重新渲染它让状态变化有路径、有记录、有边界。
在工作中,我会优先使用 Redux Toolkit,而不是手写老式 Redux 模板。
同时也会克制使用:
局部状态留在组件里
共享业务状态再放 Redux
异步请求和派生数据有清晰边界这样 Redux 才是在帮项目变清楚,而不是把所有简单状态都绕一圈。