要真正理解 Redux,我们需要跳出繁杂的代码,先看它的核心本质:Redux 是一个可预测的状态容器。
在复杂的前端应用中,组件树的层级可能非常深。如果仅依靠组件自身的 state 和 props,数据的传递(比如从祖父组件传到孙子组件)会变得极其冗长且难以追踪。Redux 的解法是:把应用的状态抽离出来,统一放到一个全局的“仓库”中管理,并制定严格的规则来修改它。
Redux 的三大核心概念
支撑 Redux 运转的只有三个核心要素,它们构成了一个单向数据流:
-
Store(仓库):应用中唯一的全局状态树。它是只读的,你不能直接去修改里面的值。
-
Action(动作):一个描述“发生了什么”的普通 JavaScript 对象。它是修改 Store 的唯一途径。比如:
{ type: 'counter/increment', payload: 1 }。 -
Reducer(处理器):一个纯函数。它接收当前的 state 和触发的 action,经过计算后,返回一个全新的 state。它不能有任何副作用(如网络请求或修改输入参数)。
数据流向始终是:UI 触发 Action -> Reducer 处理 Action 并返回新 State -> Store 更新 -> UI 重新渲染。
现代 Redux 的实际用法 (Redux Toolkit)
在过去,写 Redux 意味着要写大量的样板代码(Action Types, Action Creators, Switch/Case 等)。为了解决这个问题,官方推出了 Redux Toolkit (RTK),这也是目前工业界唯一推荐的 Redux 编写方式。
下面我们以一个最常见的“购物车计数器”为例,结合 React,手把手演示现代 Redux 的用法。
1. 定义状态和逻辑:创建 Slice
在 RTK 中,我们用 createSlice 把 Action 和 Reducer 结合在一起写,按功能模块划分(称为 Slice)。
JavaScript
// features/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter', // Slice 的名称,用于自动生成 action type
initialState: {
value: 0
},
reducers: {
// 这里的每个方法都会自动生成对应的 Action 和 Reducer
increment: (state) => {
// RTK 内部使用了 Immer 库,所以你可以看似“直接”修改 state,
// 底层会自动帮你生成全新的不可变状态,无需再写 {...state}。
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
// action.payload 就是外部传进来的数据
state.value += action.payload;
}
}
});
// 导出生成的 actions,供组件调用
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出 reducer,供配置 Store 使用
export default counterSlice.reducer;
2. 组装仓库:配置 Store
将各个模块的 Reducer 汇总到一个统一的 Store 中。
JavaScript
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counterSlice';
export const store = configureStore({
reducer: {
// 这里可以挂载多个不同模块的 state,比如 user, cart 等
counter: counterReducer,
},
});
3. 注入 React 应用:Provide Store
在应用的入口点,通过 Provider 将 Store 提供给整个 React 组件树。
JavaScript
// index.js (或 main.jsx)
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
);
4. 在组件中使用:读取状态与触发动作
借助 react-redux 提供的两个 Hooks:useSelector(读数据)和 useDispatch(发动作),你的组件就能和全局 Store 互动了。
JavaScript
// App.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './features/counterSlice';
function App() {
// 从 Store 中选取你需要的那部分 state
const count = useSelector((state) => state.counter.value);
// 获取 dispatch 函数
const dispatch = useDispatch();
return (
<div>
<h2>当前计数: {count}</h2>
{/* 派发 increment 动作 */}
<button onClick={() => dispatch(increment())}>+1</button>
{/* 派发 decrement 动作 */}
<button onClick={() => dispatch(decrement())}>-1</button>
{/* 派发带参数的动作 */}
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
export default App;
深入思考:你真的需要 Redux 吗?
Redux 提供了一种极其严谨的、可追溯的状态变更机制(结合 Redux DevTools,你可以进行时光旅行调试)。但这种严谨是有成本的,它引入了额外的复杂度和间接层。
-
什么时候必须用:应用中有大量全局共享的状态;状态的更新逻辑非常复杂;多人协作的大型项目,需要严格的架构约束;需要追踪状态的历史变更。
-
什么时候不需要用:只有简单的 UI 状态切换;数据只在父子组件间进行少量传递;仅仅是为了解决一两层的 Prop Drilling(这时候 React 原生的 Context API 就足够了)。
理解一个工具的边界,比仅仅学会如何使用它更重要。