state-management-advisor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseState Management Advisor
状态管理指南
Choose and implement the right state management solution for React applications.
为React应用选择并实现合适的状态管理方案。
Quick Start
快速入门
Use local state by default, Context for simple global state, Zustand for complex client state, and TanStack Query for server state.
默认使用本地状态,简单全局状态用Context,复杂客户端状态用Zustand,服务端状态用TanStack Query。
Instructions
使用说明
Decision Tree
决策树
Start here:
- Is it server data (from API)? → Use TanStack Query
- Is it used in one component only? → Use useState
- Is it simple global state (theme, auth)? → Use Context
- Is it complex client state? → Use Zustand or Redux Toolkit
从这里开始:
- 是否是服务端数据(来自API)?→ 使用TanStack Query
- 是否仅在单个组件中使用?→ 使用useState
- 是否是简单全局状态(主题、权限)?→ 使用Context
- 是否是复杂客户端状态?→ 使用Zustand或Redux Toolkit
Local State (useState)
本地状态(useState)
For component-specific state.
Basic usage:
tsx
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}With objects:
tsx
function Form() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const handleChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return (
<form>
<input
value={formData.name}
onChange={e => handleChange('name', e.target.value)}
/>
</form>
);
}适用于组件专属的状态管理。
基础用法:
tsx
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}处理对象:
tsx
function Form() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const handleChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
return (
<form>
<input
value={formData.name}
onChange={e => handleChange('name', e.target.value)}
/>
</form>
);
}React Context
React Context
For simple global state shared across components.
Setup:
tsx
interface AuthContextValue {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (credentials: Credentials) => {
const user = await api.login(credentials);
setUser(user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}Usage:
tsx
function App() {
return (
<AuthProvider>
<Router />
</AuthProvider>
);
}
function Profile() {
const { user, logout } = useAuth();
return <div>{user?.name} <button onClick={logout}>Logout</button></div>;
}Optimization - Split contexts:
tsx
// Separate frequently changing data
const ThemeContext = createContext<Theme>(null);
const ThemeUpdateContext = createContext<(theme: Theme) => void>(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<ThemeUpdateContext.Provider value={setTheme}>
{children}
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
);
}适用于跨组件共享的简单全局状态。
配置:
tsx
interface AuthContextValue {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (credentials: Credentials) => {
const user = await api.login(credentials);
setUser(user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}使用:
tsx
function App() {
return (
<AuthProvider>
<Router />
</AuthProvider>
);
}
function Profile() {
const { user, logout } = useAuth();
return <div>{user?.name} <button onClick={logout}>Logout</button></div>;
}优化 - 拆分Context:
tsx
// 分离频繁变化的数据
const ThemeContext = createContext<Theme>(null);
const ThemeUpdateContext = createContext<(theme: Theme) => void>(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<ThemeUpdateContext.Provider value={setTheme}>
{children}
</ThemeUpdateContext.Provider>
</ThemeContext.Provider>
);
}Zustand
Zustand
Lightweight global state with minimal boilerplate.
Installation:
bash
npm install zustandBasic store:
tsx
import { create } from 'zustand';
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// Usage
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return <button onClick={increment}>Count: {count}</button>;
}With async actions:
tsx
interface UserStore {
users: User[];
loading: boolean;
fetchUsers: () => Promise<void>;
}
const useUserStore = create<UserStore>((set) => ({
users: [],
loading: false,
fetchUsers: async () => {
set({ loading: true });
const users = await api.fetchUsers();
set({ users, loading: false });
},
}));With middleware:
tsx
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
token: null,
setToken: (token) => set({ token }),
}),
{
name: 'auth-storage',
}
)
);轻量级全局状态管理工具,样板代码极少。
安装:
bash
npm install zustand基础Store:
tsx
import { create } from 'zustand';
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
}
const useCounterStore = create<CounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
// 使用
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return <button onClick={increment}>Count: {count}</button>;
}异步操作:
tsx
interface UserStore {
users: User[];
loading: boolean;
fetchUsers: () => Promise<void>;
}
const useUserStore = create<UserStore>((set) => ({
users: [],
loading: false,
fetchUsers: async () => {
set({ loading: true });
const users = await api.fetchUsers();
set({ users, loading: false });
},
}));中间件:
tsx
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
token: null,
setToken: (token) => set({ token }),
}),
{
name: 'auth-storage',
}
)
);Redux Toolkit
Redux Toolkit
For complex state with many interactions.
Installation:
bash
npm install @reduxjs/toolkit react-reduxSetup store:
tsx
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;Provider:
tsx
import { Provider } from 'react-redux';
function App() {
return (
<Provider store={store}>
<Router />
</Provider>
);
}Usage:
tsx
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
Count: {count}
</button>
);
}Async with createAsyncThunk:
tsx
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk(
'users/fetch',
async () => {
const response = await api.fetchUsers();
return response.data;
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { data: [], loading: false },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
},
});适用于存在大量交互的复杂状态管理。
安装:
bash
npm install @reduxjs/toolkit react-redux配置Store:
tsx
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;Provider:
tsx
import { Provider } from 'react-redux';
function App() {
return (
<Provider store={store}>
<Router />
</Provider>
);
}使用:
tsx
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
Count: {count}
</button>
);
}异步操作(createAsyncThunk):
tsx
import { createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk(
'users/fetch',
async () => {
const response = await api.fetchUsers();
return response.data;
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { data: [], loading: false },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
},
});TanStack Query (React Query)
TanStack Query(React Query)
For server state management.
Installation:
bash
npm install @tanstack/react-querySetup:
tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
}Fetching data:
tsx
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <UserList users={data} />;
}Mutations:
tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<button onClick={() => mutation.mutate({ name: 'John' })}>
Create User
</button>
);
}With parameters:
tsx
function User({ id }: { id: string }) {
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});
return <div>{data?.name}</div>;
}Optimistic updates:
tsx
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users'] });
const previousUsers = queryClient.getQueryData(['users']);
queryClient.setQueryData(['users'], (old) => [...old, newUser]);
return { previousUsers };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(['users'], context.previousUsers);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});用于服务端状态管理。
安装:
bash
npm install @tanstack/react-query配置:
tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
}数据获取:
tsx
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <UserList users={data} />;
}数据变更:
tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<button onClick={() => mutation.mutate({ name: 'John' })}>
Create User
</button>
);
}带参数:
tsx
function User({ id }: { id: string }) {
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});
return <div>{data?.name}</div>;
}乐观更新:
tsx
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['users'] });
const previousUsers = queryClient.getQueryData(['users']);
queryClient.setQueryData(['users'], (old) => [...old, newUser]);
return { previousUsers };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(['users'], context.previousUsers);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});Jotai
Jotai
Atomic state management.
Installation:
bash
npm install jotaiBasic atoms:
tsx
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}Derived atoms:
tsx
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
function Display() {
const [doubleCount] = useAtom(doubleCountAtom);
return <div>Double: {doubleCount}</div>;
}Async atoms:
tsx
const usersAtom = atom(async () => {
const response = await fetch('/api/users');
return response.json();
});
function Users() {
const [users] = useAtom(usersAtom);
return <UserList users={users} />;
}原子化状态管理工具。
安装:
bash
npm install jotai基础原子:
tsx
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}派生原子:
tsx
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
function Display() {
const [doubleCount] = useAtom(doubleCountAtom);
return <div>Double: {doubleCount}</div>;
}异步原子:
tsx
const usersAtom = atom(async () => {
const response = await fetch('/api/users');
return response.json();
});
function Users() {
const [users] = useAtom(usersAtom);
return <UserList users={users} />;
}Comparison Matrix
方案对比矩阵
| Solution | Best For | Pros | Cons |
|---|---|---|---|
| useState | Local state | Simple, built-in | Limited to component |
| Context | Simple global state | Built-in, no deps | Can cause re-renders |
| Zustand | Complex client state | Minimal, fast | Smaller ecosystem |
| Redux Toolkit | Large apps, teams | Powerful, DevTools | Verbose, learning curve |
| TanStack Query | Server state | Caching, sync | Not for client state |
| Jotai | Atomic state | Fine-grained, modern | Smaller ecosystem |
| 方案 | 最佳适用场景 | 优点 | 缺点 |
|---|---|---|---|
| useState | 本地状态 | 简单、内置 | 仅限单个组件使用 |
| Context | 简单全局状态 | 内置、无依赖 | 可能导致不必要重渲染 |
| Zustand | 复杂客户端状态 | 轻量、快速 | 生态系统较小 |
| Redux Toolkit | 大型应用、团队协作 | 功能强大、支持DevTools | 代码繁琐、学习曲线陡 |
| TanStack Query | 服务端状态 | 缓存、同步机制完善 | 不适用于客户端状态 |
| Jotai | 原子化状态 | 细粒度控制、现代化 | 生态系统较小 |
Common Patterns
常见模式
Combining Solutions
方案组合使用
TanStack Query + Zustand:
tsx
// Server state with TanStack Query
const { data: users } = useQuery(['users'], fetchUsers);
// Client state with Zustand
const selectedUserId = useStore((state) => state.selectedUserId);Context + TanStack Query:
tsx
function AuthProvider({ children }) {
const { data: user } = useQuery(['me'], fetchCurrentUser);
return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
}TanStack Query + Zustand:
tsx
// 用TanStack Query管理服务端状态
const { data: users } = useQuery(['users'], fetchUsers);
// 用Zustand管理客户端状态
const selectedUserId = useStore((state) => state.selectedUserId);Context + TanStack Query:
tsx
function AuthProvider({ children }) {
const { data: user } = useQuery(['me'], fetchCurrentUser);
return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
}Form State
表单状态
Controlled with useState:
tsx
function Form() {
const [values, setValues] = useState({ name: '', email: '' });
const handleSubmit = (e) => {
e.preventDefault();
api.submit(values);
};
return (
<form onSubmit={handleSubmit}>
<input
value={values.name}
onChange={e => setValues(prev => ({ ...prev, name: e.target.value }))}
/>
</form>
);
}With form library:
tsx
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => api.submit(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
</form>
);
}用useState实现受控表单:
tsx
function Form() {
const [values, setValues] = useState({ name: '', email: '' });
const handleSubmit = (e) => {
e.preventDefault();
api.submit(values);
};
return (
<form onSubmit={handleSubmit}>
<input
value={values.name}
onChange={e => setValues(prev => ({ ...prev, name: e.target.value }))}
/>
</form>
);
}结合表单库:
tsx
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => api.submit(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
</form>
);
}Troubleshooting
问题排查
Context causing re-renders:
- Split into multiple contexts
- Use useMemo for context value
- Consider Zustand instead
Stale data in TanStack Query:
- Adjust staleTime and cacheTime
- Use refetchInterval for polling
- Invalidate queries after mutations
Redux boilerplate:
- Use Redux Toolkit (not legacy Redux)
- Use createSlice for reducers
- Use createAsyncThunk for async
State updates not reflecting:
- Check if mutating state directly
- Use functional updates
- Verify dependencies in useEffect
Context导致不必要重渲染:
- 拆分为多个Context
- 对Context值使用useMemo
- 考虑替换为Zustand
TanStack Query中数据过期:
- 调整staleTime和cacheTime
- 使用refetchInterval实现轮询
- 数据变更后失效对应查询
Redux样板代码过多:
- 使用Redux Toolkit(而非传统Redux)
- 用createSlice创建reducer
- 用createAsyncThunk处理异步操作
状态更新未生效:
- 检查是否直接修改了状态
- 使用函数式更新
- 验证useEffect中的依赖项