理解 React 中的 Redux:像专业人士一样管理状态

你是否在 React 应用程序中跨多个组件管理状态时遇到困难?随着应用程序的增长,通过多层组件传递 props(prop 钻取)变得笨重且容易出错。Redux 通过提供一个集中式的状态管理系统来解决这个问题,使你的 React 应用程序更加可预测且更易于调试。
在这份全面指南中,你将了解 Redux 如何与 React 协同工作,何时使用它,以及如何在项目中有效地实施它。我们将涵盖从核心概念到使用 Redux Toolkit(编写 Redux 代码的现代方式)的实际实现的所有内容。
关键要点
- Redux 为 React 应用程序提供集中式状态管理
- 当你的应用在许多组件之间共享复杂状态时,使用 Redux
- Redux 遵循单向数据流:actions → reducers → store → UI
- Redux Toolkit 通过减少样板代码简化 Redux 开发
- 性能优化对于较大的 Redux 应用程序至关重要
React 中的 Redux 是什么?
Redux 是一个用于 JavaScript 应用程序的可预测状态容器,在 React 中特别流行。它将应用程序的整个状态存储在一个名为""store""的单一不可变对象中。这种集中式的状态管理方法解决了 React 应用程序中的许多常见问题:
- Prop 钻取:不再需要通过多层组件传递状态
- 状态同步:确保组件之间的状态一致
- 可预测的更新:状态变化遵循严格的单向数据流
- 调试:使状态变化可跟踪和可预测
Redux 本身不是 React 的一部分,但通过 React Redux(React 的官方 Redux UI 绑定库)作为补充库工作。
// Basic Redux integration with React
import { Provider } from 'react-redux';
import store from './store';
function App() {
return (
<Provider store={store}>
<YourApplication />
</Provider>
);
}
什么时候应该使用 Redux?
并非每个 React 应用程序都需要 Redux。考虑在以下情况使用 Redux:
- 你的应用有在多个组件之间共享的复杂状态逻辑
- 组件需要从组件树的不同部分访问和更新状态
- 你需要跟踪和调试整个应用程序中的状态变化
- 你的应用有中型到大型代码库,有多个开发人员
对于较小的应用程序或具有本地化状态的组件,React 内置的状态管理(useState、useReducer 和 Context API)通常就足够了。
Redux 核心概念
Store:单一真相来源
Redux store 保存应用程序的整个状态树。与分布在组件中的 React 组件状态不同,Redux 将所有状态集中在一个地方:
// Creating a Redux store
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers';
const store = configureStore({
reducer: rootReducer
});
export default store;
Actions:描述状态变化
Actions 是描述应用程序中发生了什么的普通 JavaScript 对象。它们是向 Redux store 发送数据的唯一方式:
// An action object
{
type: 'counter/incremented',
payload: 1
}
// Action creator function
const increment = (amount) => {
return {
type: 'counter/incremented',
payload: amount
}
}
每个 action 必须有一个描述其类型的 type
属性。payload
属性包含 action 所需的任何数据。
Reducers:用于状态更新的纯函数
Reducers 是接收当前状态和一个 action,然后返回新状态的纯函数。它们指定应用程序的状态如何响应 actions 而变化:
// A simple reducer function
const counterReducer = (state = 0, action) => {
switch (action.type) {
case 'counter/incremented':
return state + action.payload;
case 'counter/decremented':
return state - action.payload;
default:
return state;
}
}
Reducers 必须:
- 是纯函数(无副作用)
- 永不直接修改状态
- 当发生变化时返回一个新的状态对象
单向数据流
Redux 遵循严格的单向数据流:
- 你从组件中分发一个 action
- Redux store 将 action 传递给 reducer
- Reducer 基于 action 创建一个新状态
- Store 更新其状态并通知所有连接的组件
- 组件使用新状态重新渲染
这种单向流使状态变化可预测且更容易理解。
React Redux 集成
Provider:将 Redux 连接到 React
Provider
组件使 Redux store 对应用程序中的所有组件可用:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Hooks:在组件中访问 Redux
React Redux 提供了让组件与 Redux store 交互的钩子:
useSelector:读取状态
import { useSelector } from 'react-redux';
function CounterDisplay() {
// Select the counter value from the store
const count = useSelector(state => state.counter.value);
return <div>Current count: {count}</div>;
}
useSelector
钩子:
- 接受一个从 store 状态中提取数据的选择器函数
- 当选择的状态变化时重新渲染组件
- 通过避免不必要的重新渲染来优化性能
useDispatch:更新状态
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<div>Count: {count}</div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
useDispatch
钩子:
- 返回 store 的 dispatch 函数
- 让你从任何组件分发 actions
使用 Redux Toolkit 的现代 Redux
Redux Toolkit 是编写 Redux 逻辑的官方推荐方式。它通过以下方式简化 Redux 开发:
- 减少样板代码
- 包含常见模式的有用工具
- 为 store 设置提供良好的默认值
- 在 reducers 中启用直接状态修改(通过 Immer)
使用 configureStore 创建 Store
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
import todosReducer from './features/todos/todosSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer
}
});
export default store;
使用 createSlice 定义状态逻辑
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write ""mutating"" logic in reducers
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
使用 createAsyncThunk 处理异步操作
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// Create an async thunk for fetching data
export const fetchUserData = createAsyncThunk(
'users/fetchUserData',
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUserData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
export default userSlice.reducer;
实际实现示例
让我们使用 Redux Toolkit 构建一个简单的计数器应用程序:
1. 设置 store
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
2. 为计数器功能创建一个 slice
// features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// Selector
export const selectCount = (state) => state.counter.value;
3. 将 store 连接到 React
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
4. 在组件中使用 Redux
// features/counter/Counter.js
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
increment,
decrement,
incrementByAmount,
selectCount
} from './counterSlice';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
<div>
<input
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
</div>
</div>
);
}
性能优化技术
使用 Reselect 的记忆化选择器
Reselect(包含在 Redux Toolkit 中)让你创建记忆化的选择器函数,只有在输入改变时才重新计算结果:
import { createSelector } from '@reduxjs/toolkit';
// Basic selectors
const selectItems = state => state.items;
const selectFilter = state => state.filter;
// Memoized selector
export const selectFilteredItems = createSelector(
[selectItems, selectFilter],
(items, filter) => {
// This calculation only runs when items or filter changes
return items.filter(item => item.includes(filter));
}
);
避免不必要的渲染
为防止组件在状态的不相关部分更改时重新渲染:
- 只选择组件需要的特定数据
- 对派生数据使用记忆化选择器
- 在选择对象时使用
shallowEqual
函数作为useSelector
的第二个参数
import { useSelector, shallowEqual } from 'react-redux';
// This component will only re-render when user.name or user.email changes
function UserInfo() {
const { name, email } = useSelector(state => ({
name: state.user.name,
email: state.user.email
}), shallowEqual);
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
Redux 与其他状态管理解决方案的比较
Redux vs. Context API
Context API:
- 内置于 React
- 对小型到中型应用程序更简单
- 不需要额外的库
- 更少的样板代码
- 有限的调试工具
Redux:
- 对复杂状态逻辑更强大
- 对频繁更新有更好的性能
- 使用 Redux DevTools 进行出色的调试
- 支持中间件处理副作用
- 更结构化的状态管理方法
Redux vs. Zustand/Recoil/MobX
在以下情况下考虑这些替代方案:
- 你需要一个更简单的 API,减少样板代码(Zustand)
- 你想要基于原子的状态管理(Recoil)
- 你更喜欢更面向对象、响应式的方法(MobX)
当你需要以下内容时,Redux 仍然是最佳选择:
- 成熟、经过实战检验的解决方案
- 出色的调试能力
- 中间件和扩展的大型生态系统
- 复杂应用程序的可预测状态管理
结论
Redux 为 React 应用程序提供了强大且可预测的状态管理解决方案。虽然它引入了一些复杂性和样板代码,但 Redux Toolkit 显著简化了开发体验。对于在多个组件之间共享状态的复杂应用程序,Redux 在可维护性、调试和可预测性方面提供了明显的好处。
在选择状态管理解决方案时,请仔细考虑应用程序的需求。对于更简单的应用程序,React 内置的状态管理可能就足够了,但随着应用程序复杂性的增加,Redux 的结构化方法变得越来越有价值。
常见问题
对于复杂的应用程序,特别是有频繁更新和共享状态的应用程序,使用 Redux。对于更简单的应用程序或当你需要共享不经常变化的值时,使用 Context。
Redux 有一些可能需要时间理解的核心概念,但 Redux Toolkit 显著简化了学习曲线。从小例子开始,逐渐增加复杂性。
不一定。虽然 Redux 增加了一些开销,但它包含性能优化,实际上可以通过防止不必要的重新渲染来提高性能。
使用中间件如 Redux Thunk(包含在 Redux Toolkit 中)或对于更复杂的异步流程使用 Redux Saga。Redux Toolkit 的 createAsyncThunk 使异步操作变得简单明了。
是的。虽然像 useReducer 和 useContext 这样的钩子可以处理更简单的状态管理需求,但 Redux 在管理复杂的应用程序状态方面仍然表现出色,特别是在大型团队中。
使用 Redux DevTools 扩展来检查状态、actions 和随时间的状态变化。这是 Redux 最强大的功能之一。