action 是一个描述发生了什么的普通对象;reducer 是一个纯函数,它接收当前 state 和一个 action,并返回下一个 state。两者结合实现了可预测的状态转换。
Action —— 描述事件
js
{ : , : { : , : } }
{ : , : }
Action 本身并不做任何事——它们是对事件的声明式描述。把它们命名为过去时态的事实("added"、"toggled")正反映了它们记录的是已经发生的事情。
function todosReducer(state = [], action) {
switch (action.type) {
case "todos/added":
return [...state, action.payload]; // new array
case "todos/toggled":
return state.map(t => // new array, new object for the changed one
t.id === action.payload ? { ...t, done: !t.done } : t
);
default:
return state; // unknown action → unchanged
}
}
reducer 的签名是 (state, action) => newState。它必须(以不可变的方式)返回一个新的 state 对象/数组,绝不能修改输入。
纯函数满足:(1)对相同的输入返回相同的输出,以及(2)没有副作用。reducer 必须遵守这两条规则:
// ❌ NOT pure — breaks everything
function badReducer(state, action) {
state.value++; // ❌ mutates input (breaks change detection, time-travel)
fetch("/api/log"); // ❌ side effect (non-deterministic, untestable)
return { value: Math.random() }; // ❌ non-deterministic output
}
纯函数特性使 Redux 的标志性能力成为可能:
✓ Predictability — same state + action ALWAYS gives the same result
✓ Time-travel / replay — you can re-run actions to reproduce any state
✓ Testability — just call reducer(state, action) and assert; no mocks needed
✓ Change detection — returning new references lets the UI detect changes
副作用(API 调用、日志记录、随机性)应放在中间件(thunk/saga)或 effect 中——绝不能放在 reducer 里。
action/reducer 模型是 Redux 风格状态管理的核心,也是它如此易于调试和测试的原因。
纯函数的要求并非随意而定:它正是让时间旅行调试、action 回放以及简单的单元测试成为可能的基础,也是为什么副作用必须放在别处的原因。
这种由纯函数驱动、以不可变方式更新的模式在 Redux、NgRx、useReducer 等中反复出现——理解它(以及保持 reducer 纯净的纪律)是实现可预测状态的根本所在。