名称: 状态管理顾问 描述: 选择并实施React状态管理解决方案,包括Context、Zustand、Redux Toolkit、TanStack Query和Jotai。用于在React应用中选择状态管理、实施全局状态或管理服务器状态时使用。
状态管理顾问
选择并实施适用于React应用的正确状态管理解决方案。
快速开始
默认使用本地状态,Context用于简单全局状态,Zustand用于复杂客户端状态,TanStack Query用于服务器状态。
说明
决策树
从这里开始:
- 是否是服务器数据(来自API)? → 使用TanStack Query
- 是否仅在一个组件中使用? → 使用useState
- 是否是简单全局状态(主题、认证)? → 使用Context
- 是否是复杂客户端状态? → 使用Zustand或Redux Toolkit
本地状态(useState)
用于组件特定状态。
基本用法:
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
计数: {count}
</button>
);
}
使用对象:
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
用于在组件间共享的简单全局状态。
设置:
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必须用于AuthProvider内部');
return context;
}
用法:
function App() {
return (
<AuthProvider>
<Router />
</AuthProvider>
);
}
function Profile() {
const { user, logout } = useAuth();
return <div>{user?.name} <button onClick={logout}>登出</button></div>;
}
优化 - 拆分上下文:
// 分离频繁变化的数据
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
轻量级全局状态,具有最小样板代码。
安装:
npm install zustand
基本存储:
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}</button>;
}
异步操作:
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 });
},
}));
中间件:
import { persist } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
token: null,
setToken: (token) => set({ token }),
}),
{
name: 'auth-storage',
}
)
);
Redux Toolkit
用于具有许多交互的复杂状态。
安装:
npm install @reduxjs/toolkit react-redux
设置存储:
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;
提供者:
import { Provider } from 'react-redux';
function App() {
return (
<Provider store={store}>
<Router />
</Provider>
);
}
用法:
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}
</button>
);
}
异步与createAsyncThunk:
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)
用于服务器状态管理。
安装:
npm install @tanstack/react-query
设置:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
}
获取数据:
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} />;
}
突变:
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' })}>
创建用户
</button>
);
}
带参数:
function User({ id }: { id: string }) {
const { data } = useQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});
return <div>{data?.name}</div>;
}
乐观更新:
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
原子状态管理。
安装:
npm install jotai
基本原子:
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount(count + 1)}>
计数: {count}
</button>
);
}
派生原子:
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
function Display() {
const [doubleCount] = useAtom(doubleCountAtom);
return <div>双倍: {doubleCount}</div>;
}
异步原子:
const usersAtom = atom(async () => {
const response = await fetch('/api/users');
return response.json();
});
function Users() {
const [users] = useAtom(usersAtom);
return <UserList users={users} />;
}
比较矩阵
| 解决方案 | 最适用场景 | 优点 | 缺点 |
|---|---|---|---|
| useState | 本地状态 | 简单,内置 | 仅限于组件 |
| Context | 简单全局状态 | 内置,无依赖 | 可能导致重新渲染 |
| Zustand | 复杂客户端状态 | 最小化,快速 | 生态系统较小 |
| Redux Toolkit | 大型应用,团队 | 强大,开发工具 | 冗长,学习曲线 |
| TanStack Query | 服务器状态 | 缓存,同步 | 不适用于客户端状态 |
| Jotai | 原子状态 | 细粒度,现代 | 生态系统较小 |
常见模式
组合解决方案
TanStack Query + Zustand:
// 服务器状态使用TanStack Query
const { data: users } = useQuery(['users'], fetchUsers);
// 客户端状态使用Zustand
const selectedUserId = useStore((state) => state.selectedUserId);
Context + TanStack Query:
function AuthProvider({ children }) {
const { data: user } = useQuery(['me'], fetchCurrentUser);
return (
<AuthContext.Provider value={{ user }}>
{children}
</AuthContext.Provider>
);
}
表单状态
使用useState控制:
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>
);
}
使用表单库:
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>
);
}
故障排除
Context导致重新渲染:
- 拆分为多个上下文
- 使用useMemo优化上下文值
- 考虑改用Zustand
TanStack Query中数据过时:
- 调整staleTime和cacheTime
- 使用refetchInterval进行轮询
- 突变后无效查询
Redux样板代码多:
- 使用Redux Toolkit(而非传统Redux)
- 使用createSlice创建reducer
- 使用createAsyncThunk处理异步
状态更新不反映:
- 检查是否直接突变状态
- 使用函数式更新
- 验证useEffect中的依赖项