React Redux 与 Zustand
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 APItry {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 确保类型安全。
-
合理使用选择器和中间件优化性能。
-