Redux
一、Redux 核心概念
1. 为什么需要 Redux?
-
解决的问题:在大型 React 应用中,跨组件共享状态、管理复杂数据流。
-
优势:
-
单一数据源:全局状态集中存储在
Store
中。 -
可预测性:通过严格的规则(纯函数、不可变性)管理状态变化。
-
调试友好:支持时间旅行调试(Redux DevTools)。
-
中间件支持:处理异步逻辑(如 API 调用)。
-
2. Redux 三大原则
原则 | 说明 |
---|---|
单一数据源 | 整个应用的状态存储在唯一的 Store 对象树中。 |
状态只读 | 只能通过 dispatch(action) 修改状态,禁止直接修改。 |
使用纯函数修改状态 | 通过 Reducer 函数接收旧状态和 Action ,返回新状态(无副作用)。 |
3. 核心概念
概念 | 作用 |
---|---|
Store | 全局状态容器,通过 createStore 创建。 |
Action | 描述状态变化的普通对象,必须包含 type 字段。 |
Reducer | 纯函数,接收当前 state 和 action ,返回新的 state 。 |
Dispatch | 触发状态更新的方法,store.dispatch(action) 。 |
Middleware | 扩展 Redux 功能(如处理异步操作),位于 dispatch 和 Reducer 之间。 |
二、Redux 与 React 集成(React-Redux)
1. 安装依赖
npm install redux react-redux @reduxjs/toolkit
2. 核心 API
-
Provider
:包裹根组件,将Store
传递给子组件。 -
useSelector
:从Store
中读取状态(替代mapStateToProps
)。 -
useDispatch
:获取dispatch
方法(替代mapDispatchToProps
)。
三、Redux 使用步骤(代码示例)
1. 定义 Reducer 和 Action
// src/store/counterSlice.js(使用 Redux Toolkit)
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // Redux Toolkit 允许直接修改(内部使用 Immer)
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
2. 创建 Store
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
3. 将 Store 注入 React 应用
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
4. 在组件中访问状态和触发 Action
// src/components/Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
export default Counter;
四、异步操作与中间件
1. 使用 Redux Thunk(处理异步逻辑)
// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
// 定义异步 Thunk
export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => {
const response = await axios.get(`https://api.example.com/users/${userId}`);
return response.data;
});
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false, error: null },
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, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export default userSlice.reducer;
2. 组件中调用异步 Action
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading, error } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUser(userId));
}, [dispatch, userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>Username: {data.name}</div>;
}
Redux Thunk 完整使用流程:用户数据请求
1. 创建异步 Thunk
// src/store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
// 创建异步 Thunk Action
export const fetchUser = createAsyncThunk(
'user/fetchUser', // 唯一标识符(推荐格式:"slice名称/action名称")
async (userId, thunkAPI) => { // 接收参数和 Thunk API
try {
const response = await axios.get(`https://api.example.com/users/${userId}`);
return response.data; // 成功时返回数据作为 payload
} catch (error) {
return thunkAPI.rejectWithValue(error.message); // 失败时传递错误信息
}
}
);
关键点:
-
createAsyncThunk
会自动生成三种 action 类型:-
user/fetchUser/pending
(请求开始) -
user/fetchUser/fulfilled
(请求成功) -
user/fetchUser/rejected
(请求失败)
-
-
参数说明:
-
第一个参数:唯一标识符(建议用
slice名称/action名称
格式) -
第二个参数:异步处理函数(可接收参数和
thunkAPI
对象)
-
2. 创建 Slice 处理状态
const userSlice = createSlice({
name: 'user', // slice 名称
initialState: { // 初始状态
data: null, // 用户数据
loading: false, // 加载状态
error: null // 错误信息
},
extraReducers: (builder) => {
builder
// 处理 pending 状态(请求开始)
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null; // 重置错误
})
// 处理 fulfilled 状态(请求成功)
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload; // 存储返回数据
})
// 处理 rejected 状态(请求失败)
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload || action.error.message;
});
}
});
export default userSlice.reducer;
3. 配置 Store
// src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
export default configureStore({
reducer: {
user: userReducer // 注册 slice
}
});
4. 在组件中使用
// src/components/UserProfile.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from '../store/userSlice';
function UserProfile({ userId }) {
const dispatch = useDispatch();
const { data, loading, error } = useSelector(state => state.user);
useEffect(() => {
dispatch(fetchUser(userId)); // 触发异步请求
}, [dispatch, userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
return (
<div>
<h2>{data.name}</h2>
<p>邮箱:{data.email}</p>
</div>
);
}
export default UserProfile;
五、Redux 最佳实践
1. 项目结构
-
推荐结构(按功能模块划分):
src/ store/ slices/ // Redux Toolkit 的 Slice 文件 index.js // Store 配置 components/ // UI 组件 features/ // 包含业务逻辑的组件
2. 状态设计原则
-
最小化状态:避免冗余数据,只存储必要状态。
-
规范化数据:使用
id
作为键,避免嵌套过深(可搭配normalizr
库)。
3. 性能优化
-
使用
React.memo
:避免不必要的组件渲染。 -
选择精确的状态片段:
useSelector
尽量返回最小化数据。// ✅ 精确选择 const count = useSelector((state) => state.counter.value); // ❌ 避免返回整个 state.counter const counter = useSelector((state) => state.counter);
4. 使用 Redux Toolkit
-
优势:减少样板代码,内置
immer
(允许直接修改状态)、createAsyncThunk
等工具。 -
替代方案:手动编写
action
、reducer
和中间件配置(传统 Redux)。
六、Redux 适用场景与替代方案
1. 何时使用 Redux?
-
多个组件需要共享同一状态。
-
状态更新逻辑复杂(如跨组件联动)。
-
需要时间旅行调试、持久化状态或记录状态历史。
2. 轻量替代方案
方案 | 特点 |
---|---|
Context API | React 内置,适合简单状态共享,但缺乏中间件、性能优化工具。 |
Recoil | Facebook 实验性状态管理库,原子化状态设计,适合复杂数据流。 |
Zustand | 轻量级,基于 Hook 的状态管理,API 简洁。 |
七、总结
-
Redux 核心:
Store
、Action
、Reducer
、Middleware
。 -
React-Redux 集成:
Provider
、useSelector
、useDispatch
。 -
异步处理:通过
Redux Thunk
或createAsyncThunk
管理 API 调用。 -
最佳实践:使用 Redux Toolkit 简化代码,合理设计状态结构。
Zustand
一、Zustand 核心概念
1. 定位与特点
-
轻量级状态管理:专为 React 设计,API 简洁,学习成本低。
-
脱离组件树:状态独立于 UI 层级,可在组件外访问。
-
高性能:按需订阅状态片段,避免不必要的渲染。
-
中间件支持:集成持久化、Immer(不可变更新)、日志等。
二、基础使用
1. 创建 Store
// store/counterStore.ts
import { create } from 'zustand';
type CounterState = {
count: number;
increment: () => void;
decrement: () => void;
};
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
2. 组件中使用状态
import { useCounterStore } from './store/counterStore';
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
三、高级功能
1. 状态切片与选择器(Selector)
按需订阅部分状态,避免全局重新渲染:
// 只订阅 count 值的变化
const count = useCounterStore((state) => state.count);
2. 异步操作
在 Store 中定义异步 Action:
type UserStore = {
user: User | null;
fetchUser: (id: string) => Promise<void>;
};
export const useUserStore = create<UserStore>((set) => ({
user: null,
fetchUser: async (id) => {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user });
},
}));
3. 中间件(Middleware)
持久化存储(Persist)
import { persist } from 'zustand/middleware';
export const useAuthStore = create(
persist(
(set) => ({
token: null,
login: (token) => set({ token }),
logout: () => set({ token: null }),
}),
{ name: 'auth-storage' } // 存储到 localStorage
)
);
不可变更新(Immer)
import { immer } from 'zustand/middleware/immer';
export const useTodoStore = create(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({ text, completed: false }); // 直接修改 draft
}),
}))
);
四、最佳实践
1. 合理拆分 Store
-
按功能模块拆分:避免单一 Store 过于臃肿。
// store/userStore.ts export const useUserStore = create(...); // store/cartStore.ts export const useCartStore = create(...);
2. 类型安全(TypeScript)
为 Store 和 Actions 定义明确类型:
type UserState = {
user: User | null;
setUser: (user: User) => void;
clearUser: () => void;
};
export const useUserStore = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
}));
3. 性能优化
-
使用选择器:避免订阅无关状态。
-
结合
shallow
比较:优化对象/数组类型的状态订阅。import { shallow } from 'zustand/shallow'; const { user, setUser } = useUserStore( (state) => ({ user: state.user, setUser: state.setUser }), shallow );
4. 在组件外访问 Store
直接调用 Store 方法(如 API 模块中):
// 在非组件代码中
import { useCounterStore } from './store/counterStore';
const { increment } = useCounterStore.getState();
increment();
五、与 Redux 和 Context API 对比
特性 | Zustand | Redux | Context API |
---|---|---|---|
复杂度 | 极低 | 高(需 Action、Reducer、Middleware) | 低 |
性能 | 按需订阅状态,自动优化 | 需手动优化(如 reselect ) | 全子树渲染,需手动优化 |
中间件/插件 | 支持(持久化、Immer 等) | 丰富(Redux Thunk、Saga 等) | 无 |
适用场景 | 中小型应用,快速开发 | 大型应用,需严格架构 | 简单状态共享,低频更新 |
TypeScript | 天然支持 | 需额外配置 | 支持 |
六、常见问题与解决方案
1. Store 状态更新后组件未渲染
-
原因:组件未订阅相关状态或选择器未正确更新。
-
解决:检查选择器逻辑,确保状态更新触发组件重渲染。
2. 循环依赖问题
-
场景:多个 Store 相互依赖。
-
解决:通过
getState
在 Action 中访问其他 Store:// store/authStore.ts import { useCartStore } from './cartStore'; export const useAuthStore = create((set) => ({ login: (user) => { const { clearCart } = useCartStore.getState(); clearCart(); // 登录时清空购物车 set({ user }); }, }));
3. Zustand 与 Next.js 服务端渲染(SSR)
-
问题:服务端与客户端状态不一致。
-
解决:使用
persist
中间件 + 自定义存储(如 Cookie):import { persist, createJSONStorage } from 'zustand/middleware'; export const useStore = create( persist( (set) => ({ ... }), { name: 'store', storage: createJSONStorage(() => localStorage), // 或自定义存储 } ) );
七、总结
-
核心优势:简洁、高性能、脱离组件树、中间件支持。
-
适用场景:中小型 React 应用、需快速开发、状态共享与优化。
-
最佳实践:
-
按功能拆分 Store。
-
使用 TypeScript 确保类型安全。
-
合理使用选择器和中间件优化性能。
-