跳到主要内容

状态管理方案

问题

React 有哪些状态管理方案?Redux、Context、Zustand、Jotai、Recoil、Valtio 各有什么特点?

答案

React 状态管理方案分为多个流派:

类型代表特点
原生方案Context + useReducerReact 内置,适合简单场景
Flux 架构Redux单向数据流,严格规范
原子化Jotai、Recoil自底向上,细粒度更新
代理模式Zustand、Valtio简洁 API,易于使用

React Context

基本用法

import { createContext, useContext, useState, ReactNode } from 'react';

// 1. 创建 Context
interface ThemeContextValue {
theme: 'light' | 'dark';
toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextValue | null>(null);

// 2. 创建 Provider
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');

const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

// 3. 使用 Context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}

// 4. 消费组件
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
style={{ background: theme === 'dark' ? '#333' : '#fff' }}
>
Toggle Theme
</button>
);
}

Context 的性能问题

// ❌ 所有消费者都会重渲染
const AppContext = createContext({ user: null, theme: 'light', locale: 'en' });

function App() {
const [state, setState] = useState({ user: null, theme: 'light', locale: 'en' });

return (
<AppContext.Provider value={state}>
<UserInfo /> {/* user 变化时重渲染 */}
<ThemeSwitch /> {/* user 变化时也重渲染! */}
<LocalePicker />{/* user 变化时也重渲染! */}
</AppContext.Provider>
);
}

优化方案

方案 1:拆分 Context

// ✅ 拆分成多个 Context
const UserContext = createContext<User | null>(null);
const ThemeContext = createContext<Theme>('light');
const LocaleContext = createContext<Locale>('en');

function App() {
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<LocaleContext.Provider value={locale}>
<Content />
</LocaleContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}

方案 2:分离数据和操作

// ✅ 数据和操作分离
const StateContext = createContext<State | null>(null);
const DispatchContext = createContext<Dispatch | null>(null);

function Provider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}

// 只需要 dispatch 的组件不会因 state 变化重渲染
function AddButton() {
const dispatch = useContext(DispatchContext)!;
return <button onClick={() => dispatch({ type: 'ADD' })}>Add</button>;
}

Redux

核心概念

概念说明
Store单一数据源,存储应用状态
Action描述发生了什么的普通对象
Reducer纯函数,根据 action 返回新状态
Dispatch发送 action 到 store
Selector从 state 中提取数据

Redux Toolkit 示例

// store/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
value: number;
history: number[];
}

const initialState: CounterState = {
value: 0,
history: [],
};

const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value += 1;
state.history.push(state.value);
},
decrement(state) {
state.value -= 1;
state.history.push(state.value);
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload;
state.history.push(state.value);
},
},
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
reducer: {
counter: counterReducer,
},
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// hooks.ts
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// Counter.tsx
import { useAppDispatch, useAppSelector } from './hooks';
import { increment, decrement, incrementByAmount } from './store/counterSlice';

function Counter() {
const count = useAppSelector(state => state.counter.value);
const dispatch = useAppDispatch();

return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}

Redux 异步操作

// 使用 createAsyncThunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

interface User {
id: number;
name: string;
}

interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}

export const fetchUser = createAsyncThunk(
'user/fetch',
async (userId: number) => {
const response = await fetch(`/api/users/${userId}`);
return response.json() as Promise<User>;
}
);

const userSlice = createSlice({
name: 'user',
initialState: { user: null, loading: false, error: null } as UserState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.user = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message ?? 'Failed to fetch';
});
},
});

Zustand

轻量级状态管理,API 简洁:

import { create } from 'zustand';

// 定义 store
interface BearState {
bears: number;
increase: () => void;
decrease: () => void;
reset: () => void;
}

const useBearStore = create<BearState>((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
decrease: () => set((state) => ({ bears: state.bears - 1 })),
reset: () => set({ bears: 0 }),
}));

// 使用
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return <h1>{bears} bears</h1>;
}

function Controls() {
const increase = useBearStore((state) => state.increase);
return <button onClick={increase}>Add Bear</button>;
}

Zustand 中间件

import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

const useStore = create<State>()(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'counter-storage' } // localStorage key
)
)
);

Zustand 异步操作

interface UserStore {
user: User | null;
loading: boolean;
fetchUser: (id: number) => Promise<void>;
}

const useUserStore = create<UserStore>((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true });
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
set({ user, loading: false });
} catch {
set({ loading: false });
}
},
}));

Jotai

原子化状态管理,自底向上:

import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';

// 定义原子
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 可写派生原子
const incrementAtom = atom(
null, // 只写,不需要读
(get, set) => set(countAtom, get(countAtom) + 1)
);

// 使用
function Counter() {
const [count, setCount] = useAtom(countAtom);
const double = useAtomValue(doubleCountAtom);
const increment = useSetAtom(incrementAtom);

return (
<div>
<p>Count: {count}</p>
<p>Double: {double}</p>
<button onClick={increment}>+</button>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}

Jotai 异步原子

// 异步原子
const userAtom = atom(async () => {
const response = await fetch('/api/user');
return response.json();
});

// 使用 Suspense
function UserInfo() {
const user = useAtomValue(userAtom); // 会抛出 Promise
return <div>{user.name}</div>;
}

function App() {
return (
<Suspense fallback={<Loading />}>
<UserInfo />
</Suspense>
);
}

Jotai 组合原子

const firstNameAtom = atom('John');
const lastNameAtom = atom('Doe');

// 派生原子
const fullNameAtom = atom(
(get) => `${get(firstNameAtom)} ${get(lastNameAtom)}`,
(get, set, newName: string) => {
const [first, last] = newName.split(' ');
set(firstNameAtom, first);
set(lastNameAtom, last ?? '');
}
);

Recoil

Facebook 出品的原子化状态管理:

import { atom, selector, useRecoilState, useRecoilValue, RecoilRoot } from 'recoil';

// 原子
const todoListState = atom<Todo[]>({
key: 'todoListState',
default: [],
});

// 选择器(派生状态)
const filteredTodoListState = selector({
key: 'filteredTodoListState',
get: ({ get }) => {
const filter = get(todoListFilterState);
const list = get(todoListState);

switch (filter) {
case 'completed':
return list.filter(item => item.isComplete);
case 'uncompleted':
return list.filter(item => !item.isComplete);
default:
return list;
}
},
});

// 使用
function TodoList() {
const todoList = useRecoilValue(filteredTodoListState);
return (
<ul>
{todoList.map(todo => (
<TodoItem key={todo.id} item={todo} />
))}
</ul>
);
}

// 根组件需要包裹 RecoilRoot
function App() {
return (
<RecoilRoot>
<TodoList />
</RecoilRoot>
);
}

Recoil 异步选择器

const userQuery = selector({
key: 'userQuery',
get: async ({ get }) => {
const userId = get(currentUserIdState);
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});

// 自动支持 Suspense
function UserInfo() {
const user = useRecoilValue(userQuery);
return <div>{user.name}</div>;
}

Valtio

基于代理的响应式状态:

import { proxy, useSnapshot } from 'valtio';

// 创建代理状态
const state = proxy({
count: 0,
user: { name: 'Alice' },
});

// 直接修改(可变式 API)
const increment = () => {
state.count++;
};

const updateName = (name: string) => {
state.user.name = name;
};

// 组件中使用
function Counter() {
const snap = useSnapshot(state);
return (
<div>
<p>Count: {snap.count}</p>
<p>User: {snap.user.name}</p>
<button onClick={increment}>+</button>
</div>
);
}

Valtio 派生状态

import { proxy, derive } from 'valtio/utils';

const state = proxy({
firstName: 'John',
lastName: 'Doe',
});

// 派生状态
const derived = derive({
fullName: (get) => `${get(state).firstName} ${get(state).lastName}`,
});

// 订阅变化
import { subscribe } from 'valtio';

subscribe(state, () => {
console.log('state changed:', state);
});

方案对比

特性ReduxZustandJotaiRecoilValtioContext
包大小~12KB~1KB~3KB~20KB~3KB0
学习曲线陡峭平缓平缓中等平缓平缓
模式Flux单Store原子原子代理原生
DevTools有限
异步支持中间件内置内置内置内置手动
SSR⚠️
TypeScript
适合场景大型应用中小型细粒度细粒度中小型简单场景

常见面试问题

Q1: Redux 的三大原则是什么?

答案

  1. 单一数据源:整个应用状态存储在一个 store 中
  2. 状态只读:只能通过 dispatch action 修改状态,不能直接修改
  3. 纯函数修改:Reducer 是纯函数,相同输入产生相同输出
// 单一数据源
const store = configureStore({ reducer: rootReducer });

// 状态只读 - 通过 action 修改
dispatch({ type: 'INCREMENT' }); // ✅
// state.count++; // ❌

// 纯函数
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 }; // 返回新对象
default:
return state;
}
}

Q2: Zustand 和 Redux 的区别?

答案

特性ReduxZustand
包大小~12KB~1KB
样板代码多(action, reducer, dispatch)少(一个 create)
Provider需要 <Provider>不需要
中间件复杂配置简单组合
异步需要 thunk/saga直接 async
调试Redux DevTools同样支持
// Redux
const store = configureStore({ reducer });
dispatch(increment());

// Zustand
const useStore = create((set) => ({
count: 0,
increment: () => set((s) => ({ count: s.count + 1 })),
}));
const increment = useStore(s => s.increment);
increment();

Q3: 什么是原子化状态管理?Jotai 和 Recoil 有什么区别?

答案

原子化状态管理:将状态分割成独立的原子,每个原子只触发订阅它的组件更新。

特性JotaiRecoil
创建方式atom(initialValue)atom({ key, default })
Key 要求不需要必须唯一
包大小~3KB~20KB
API 风格更简洁更完整
维护方社区Facebook
// Jotai - 无需 key
const countAtom = atom(0);

// Recoil - 需要 key
const countState = atom({
key: 'countState',
default: 0,
});

Q4: 如何选择状态管理方案?

答案

场景推荐方案
小型应用、简单全局状态Context
需要严格规范、大团队Redux
中小型应用、追求简洁Zustand
细粒度更新、原子化Jotai
已有 Redux 经验、需要原子化Recoil
喜欢可变式 APIValtio

Q5: Context 的性能问题如何解决?

答案

问题:Provider value 变化时,所有消费者都会重渲染。

解决方案

  1. 拆分 Context:不同数据使用不同 Context
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
  1. Memo + 分离数据和操作
const StateContext = createContext(state);
const ActionsContext = createContext(actions);

// actions 用 useMemo 稳定引用
const actions = useMemo(() => ({
increment: () => dispatch({ type: 'INC' }),
}), []);
  1. 使用状态管理库
// Zustand 自动处理精细更新
const count = useStore(s => s.count); // 只订阅 count

Q6: Zustand 为什么越来越流行?它和 Redux 的核心区别?

答案

Zustand 近年来增长迅猛,npm 周下载量已接近 Redux Toolkit,主要原因是它在开发体验性能上都做得更好。

Zustand 流行的核心原因

维度Redux(含 RTK)Zustand
样板代码多(slice、action、reducer、store、Provider)极少(一个 create 搞定)
Provider必须用 <Provider> 包裹根组件不需要 Provider
包大小~12KB(RTK)~1KB
学习成本Flux 架构、中间件、RTK API几乎为零,5 分钟上手
异步处理需要 createAsyncThunk 或中间件直接在 action 里写 async
精确更新需要 useSelector + shallowEqual内置 selector,默认精确更新
React 外使用需要 store.getState()原生支持组件外访问
TypeScript类型推导复杂(RootState、AppDispatch)自动推导,几乎零配置

代码对比——实现同一个功能

Redux Toolkit 实现
// 1. 定义 slice
import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
import { useSelector, useDispatch, Provider } from 'react-redux';

interface CartState {
items: CartItem[];
total: number;
}

const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 } as CartState,
reducers: {
addItem(state, action: PayloadAction<CartItem>) {
state.items.push(action.payload);
state.total += action.payload.price;
},
removeItem(state, action: PayloadAction<number>) {
const index = state.items.findIndex(i => i.id === action.payload);
if (index !== -1) {
state.total -= state.items[index].price;
state.items.splice(index, 1);
}
},
},
});

// 2. 配置 store
const store = configureStore({ reducer: { cart: cartSlice.reducer } });
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;

// 3. 类型化 hooks
const useAppSelector = <T,>(selector: (state: RootState) => T) => useSelector(selector);
const useAppDispatch = () => useDispatch<AppDispatch>();

// 4. 使用(需要 Provider 包裹)
function CartCount() {
const total = useAppSelector(s => s.cart.total);
return <span>总价:{total}</span>;
}

function App() {
return (
<Provider store={store}>
<CartCount />
</Provider>
);
}
Zustand 实现
import { create } from 'zustand';

// 一个 create 搞定一切
interface CartStore {
items: CartItem[];
total: number;
addItem: (item: CartItem) => void;
removeItem: (id: number) => void;
}

const useCartStore = create<CartStore>((set) => ({
items: [],
total: 0,
addItem: (item) => set((s) => ({
items: [...s.items, item],
total: s.total + item.price,
})),
removeItem: (id) => set((s) => {
const item = s.items.find(i => i.id === id);
return {
items: s.items.filter(i => i.id !== id),
total: s.total - (item?.price ?? 0),
};
}),
}));

// 直接使用,无需 Provider
function CartCount() {
const total = useCartStore((s) => s.total);
return <span>总价:{total}</span>;
}

核心区别总结

什么时候仍然选择 Redux?
  • 大型团队:Redux 的严格规范(action → reducer 单向流)有利于代码审查和协作
  • 复杂中间件需求:日志、撤销重做、离线同步等复杂中间件生态 Redux 更成熟
  • 已有大量 Redux 代码:迁移成本高,继续用 Redux 更合理
  • 需要时间旅行调试:Redux DevTools 的时间旅行功能更完善

Q7: 什么时候应该用 Context,什么时候用状态管理库?

答案

这是一个非常高频的面试问题。核心判断标准是更新频率消费者数量

判断决策树

Context 适用场景

场景原因
主题切换(light/dark)低频更新,几乎不变
用户认证信息登录后基本不变
国际化语言切换频率极低
路由信息React Router 内部就用 Context
依赖注入(API client、配置)初始化后不变
Context 适合的场景
// ✅ 主题 —— 低频更新
const ThemeContext = createContext<'light' | 'dark'>('light');

// ✅ 认证 —— 登录后基本不变
const AuthContext = createContext<AuthState | null>(null);

// ✅ 配置注入 —— 初始化后不变
const ConfigContext = createContext<AppConfig>(defaultConfig);

状态管理库适用场景

场景原因
购物车频繁增删,多组件消费
表单联动字段间相互影响,更新频繁
实时数据(股票、聊天)高频更新,需精确订阅
复杂筛选/排序多维度状态交叉影响
跨页面共享多个路由页面共享同一状态
状态管理库适合的场景
// ✅ 购物车 —— 高频更新、多组件消费
const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item: CartItem) => set((s) => ({ items: [...s.items, item] })),
removeItem: (id: number) => set((s) => ({
items: s.items.filter(i => i.id !== id),
})),
get totalPrice() {
return this.items.reduce((sum, i) => sum + i.price, 0);
},
}));

// ✅ 实时聊天 —— 高频更新、精确订阅
const useChatStore = create<ChatStore>((set) => ({
messages: [],
unreadCount: 0,
addMessage: (msg: Message) => set((s) => ({
messages: [...s.messages, msg],
unreadCount: s.unreadCount + 1,
})),
}));

Context vs 状态管理库对比

维度Context状态管理库(Zustand 等)
精确订阅不支持,value 变就全更新支持 selector 精确订阅
更新性能所有消费者重渲染只有订阅了变化字段的组件重渲染
嵌套问题多 Context 容易嵌套地狱无需 Provider
React 外使用不支持支持(工具函数、中间件中)
DevTools基本没有完善的调试工具
额外依赖零依赖需要安装包
学习成本React 原生 API需学习库的 API
常见误区

"Context 是 React 的状态管理方案" —— 这个说法不准确。Context 本质是依赖注入机制,它解决的是"如何把值传递给深层组件"的问题,而不是"如何高效管理和更新状态"。Context 没有精确订阅、没有中间件、没有 DevTools,不应该把它当作状态管理库来用。

Q8: 服务端状态和客户端状态有什么区别?React Query/SWR 解决什么问题?

答案

这是现代 React 开发中非常重要的概念区分。传统做法把所有状态都放进 Redux/Zustand 管理,但实际上服务端状态和客户端状态的特性完全不同,应该用不同的工具管理。

两种状态的本质区别

维度客户端状态(Client State)服务端状态(Server State)
数据来源用户交互产生远程 API 获取
所有权前端完全控制后端拥有,前端只有"快照"
是否可能过期不会会过期(别人可能修改了)
同步需求需要与后端保持同步
示例主题、表单输入、UI 状态、模态框用户列表、商品数据、评论

传统方案的痛点

传统 Redux 管理服务端状态的问题
// ❌ 需要手动处理大量状态
interface UserState {
data: User[] | null;
loading: boolean;
error: string | null;
// 还缺少:缓存、过期、重试、分页、乐观更新...
}

const fetchUsers = createAsyncThunk('users/fetch', async () => {
const res = await fetch('/api/users');
return res.json();
});

// 每个接口都要写这一套 pending/fulfilled/rejected
const userSlice = createSlice({
name: 'users',
initialState: { data: null, loading: false, error: null } as UserState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => { state.loading = true; })
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message ?? null;
});
},
});
// 问题:缓存何时过期?标签页切回来要不要刷新?失败了自动重试吗?

React Query(TanStack Query)解决方案

React Query 管理服务端状态
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// ✅ 一行代码搞定:加载、缓存、过期、重试、去重
function UserList() {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json()) as Promise<User[]>,
staleTime: 5 * 60 * 1000, // 5 分钟内认为数据是新鲜的
gcTime: 30 * 60 * 1000, // 30 分钟后回收缓存
retry: 3, // 失败自动重试 3 次
refetchOnWindowFocus: true, // 标签页切回来自动刷新
});

if (isLoading) return <Loading />;
if (error) return <Error message={error.message} />;
return (
<ul>
{data?.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
React Query 的 Mutation(增删改)
function AddUser() {
const queryClient = useQueryClient();

const mutation = useMutation({
mutationFn: (newUser: CreateUserDTO) =>
fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
}).then(res => res.json()),
// 乐观更新:先更新 UI,再等服务端确认
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users'] });
const previous = queryClient.getQueryData<User[]>(['users']);
queryClient.setQueryData<User[]>(['users'], (old) => [
...(old ?? []),
{ ...newUser, id: Date.now() } as User,
]);
return { previous };
},
onError: (_err, _newUser, context) => {
// 失败时回滚
queryClient.setQueryData(['users'], context?.previous);
},
onSettled: () => {
// 无论成功失败,都重新获取最新数据
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});

return (
<button onClick={() => mutation.mutate({ name: 'New User', email: 'new@test.com' })}>
{mutation.isPending ? '添加中...' : '添加用户'}
</button>
);
}

SWR 对比

SWR 实现
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function UserList() {
// SWR = Stale-While-Revalidate(先用缓存,后台刷新)
const { data, error, isLoading, mutate } = useSWR<User[]>(
'/api/users',
fetcher,
{
revalidateOnFocus: true,
dedupingInterval: 2000, // 2 秒内去重
}
);

if (isLoading) return <Loading />;
if (error) return <Error />;
return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

React Query vs SWR 对比

特性React QuerySWR
包大小~13KB~4KB
Mutation 支持完善(onMutate、乐观更新)基础
DevTools✅ 专属 DevTools
离线支持✅ 内置需手动
分页/无限滚动useInfiniteQueryuseSWRInfinite
缓存管理精细(gcTime、staleTime)简单
适合场景复杂数据交互(增删改查)简单数据读取为主
现代 React 状态管理最佳实践

将状态分为两类分别管理,是目前社区的主流共识:

  • 客户端状态useState / Zustand(轻量、简单)
  • 服务端状态React Query / SWR(缓存、同步、重试)

这样 Redux 式的"全局大 Store"就可以被拆解为更小、更专注的工具组合,代码量和维护成本都会大幅下降。

相关链接