This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/3. 生态工具篇/09-状态管理.md
T
2026-04-29 22:19:51 +08:00

214 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [React, State Management, Context, Zustand, Redux, Frontend]
create time: 2026-04-29 22:08
---
# 状态管理
## 概述
当组件树变得复杂,跨层级共享状态、服务器数据同步和状态变更追踪成为挑战。本文档对比 Context API、Zustand 和 Redux Toolkit 三种主流方案,帮助你在不同场景下做出正确选择。
## 选型决策图
```mermaid
graph TD
A["需要全局状态吗?"] -->|"否"| B["useState / useReducer ✅"]
A -->|"是"| C["状态类型?"]
C --> D["纯 UI 状态<br/>(主题、菜单展开、模态框)"]
C --> E["服务器数据 / 异步缓存"]
C --> F["应用级业务状态<br/>(用户信息、购物车、权限)"]
D --> G["Context API ✅"]
E --> H["TanStack Query / SWR ✅"]
F --> I["状态规模?"]
I --> J["小型 (< 5 store)"]
I --> K["中大型"]
J --> L["Zustand ✅"]
K --> M["Redux Toolkit + RTK Query ✅"]
style G fill:#4FC08D,color:#fff
style H fill:#F5A87D,color:#000
style L fill:#61DAFB,color:#000
style M fill:#764abc,color:#fff
```
## Context API —— 轻量传递
```tsx
const AuthContext = createContext<{ user: User | null; login: (u: User) => void }>({
user: null,
login: () => {},
});
// Provider
function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = (u: User) => setUser(u);
return <AuthContext.Provider value={{ user, login }}>{children}</AuthContext.Provider>;
}
// Consumer
function Profile() {
const { user, login } = useContext(AuthContext);
return <div>Hello, {user?.name}</div>;
}
```
### Context 的性能局限
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| value 变化时所有消费组件重渲染 | Context Value 引用每次都是新的 | 拆分多个 Context / 用 reducer 保持 dispatch 引用稳定 |
| 不支持 selector | 没有 "只取子字段" 的机制 | 手动封装或使用第三方库 |
| SSR hydration mismatch | 客户端与初始值不一致 | 延迟消费或用 useEffect 包裹 |
## Zustand —— 轻量级现代方案
```ts
import { create } from "zustand";
interface StoreState {
count: number;
users: User[];
increment: () => void;
fetchUsers: () => Promise<void>;
}
const useStore = create<StoreState>((set, get) => ({
count: 0,
users: [],
increment: () => set(state => ({ count: state.count + 1 })),
fetchUsers: async () => {
const res = await fetch("/api/users");
const data = await res.json();
set({ users: data });
},
}));
// 组件中使用
function Counter() {
// ✅ 只订阅 count —— 其他状态变化不会触发此组件重渲染
const count = useStore(s => s.count);
const increment = useStore(s => s.increment);
return <button onClick={increment}>Count: {count}</button>;
}
// 批量更新
useStore.setState(({ count }) => ({ count: count + 1, flag: true }));
```
### Zustand 的优势
| 特性 | Zustand | Context |
|------|---------|---------|
| Bundle size | ~1KB | 内置 React(~0) |
| Selector 支持 | ✅ 精确订阅 | ❌ 全部消费者都重渲染 |
| TypeScript 推断 | 完善 | 需手动标注 |
| DevTools | 原生支持 | 无 |
| 中间件扩展 | persist、immer、devtools | 需额外封装 |
## Redux Toolkit —— 企业级方案
```ts
import { createSlice, configureStore, useDispatch, useSelector } from "@reduxjs/toolkit";
interface CounterSlice {
value: number;
status: "idle" | "loading" | "succeeded" | "failed";
}
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0, status: "idle" } as CounterSlice,
reducers: {
incremented: state => { state.value += 1; }, // ✅ Immer:直接 mutate!
fetchedAsync: {
pending: state => { state.status = "loading"; },
fulfilled: (state, action) => {
state.status = "succeeded";
state.value = action.payload.value;
},
rejected: state => { state.status = "failed"; },
},
},
});
export const { incremented, fetchedAsync } = counterSlice.actions;
const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
```
```tsx
function CounterComponent() {
const dispatch = useDispatch<AppDispatch>();
const count = useSelector((s: AppState) => s.counter.value);
return <button onClick={() => dispatch(incremented())}>{count}</button>;
}
```
### RTK Query —— 内置数据获取
```ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
endpoints: build => ({
getUsers: build.query<User[], void>({ query: () => "/users" }),
updateUser: build.mutation<User, Partial<User>>({
query: ({ id, ...patch }) => ({ url: `/users/${id}`, method: "PATCH", body: patch }),
}),
}),
});
export const { useGetUsersQuery, useUpdateUserMutation } = api;
```
## 三框架横向对比
| 维度 | Context API | Zustand | Redux Toolkit |
|------|-------------|---------|---------------|
| Bundle 大小 | ~0KB(内置) | ~1KB | ~15KB |
| 学习曲线 | 低 | 低 | 中 |
| Selector | ❌ | ✅ | ✅(useSelector) |
| Immutable | ❌ | ❌(但可用 immer middleware) | ✅(Immer 内置) |
| 调试工具 | ❌ | ✅ | ✅ Redux DevTools |
| 服务端渲染 | ⚠️ 需手动处理 | ✅ | ✅ |
| 适用规模 | 小型项目 | 中小/中型 | 大型企业级 |
## 常见反模式
> [!warning] 以下做法应避免
```tsx
// ❌ 把所有东西塞进一个 store
const useBigStore = create(() => ({
user: ..., theme: ..., sidebarOpen: ..., notifications: ..., cart: ..., preferences: ..., // 20+ 个状态
}));
// ✅ 按功能拆分为多个 store
const useUserStore = create(...)
const useThemeStore = create(...)
// ❌ 在 state 中存储服务器返回的数据却不做缓存
const [data, setData] = useState(fetch(...)) // 页面切换丢失,重复请求
// ✅ 使用 TanStack Query 等专用数据获取库处理缓存和失效
const { data } = useQuery({ queryKey: ["users"], queryFn: fetchUsers });
```
## 关联笔记