React 状态管理
0x01 状态管理概述
React 提供了多种状态管理方案,从简单的本地状态到全局状态管理库。理解不同场景下如何选择合适的状态管理方式,是构建可维护 React 应用的关键。
0x02 本地状态管理
useState
适合简单的 UI 状态:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
useReducer
适合复杂的状态逻辑:
import { useReducer } from 'react';
interface State {
isLoading: boolean;
data: any;
error: string | null;
}
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: any }
| { type: 'FETCH_ERROR'; error: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'FETCH_START':
return { isLoading: true, data: null, error: null };
case 'FETCH_SUCCESS':
return { isLoading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { isLoading: false, data: null, error: action.error };
default:
return state;
}
}
function DataFetcher() {
const [state, dispatch] = useReducer(reducer, {
isLoading: false,
data: null,
error: null
});
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const data = await api.getData();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (err) {
dispatch({ type: 'FETCH_ERROR', error: err.message });
}
};
return (
<div>
<button onClick={fetchData} disabled={state.isLoading}>
{state.isLoading ? 'Loading...' : 'Fetch Data'}
</button>
{state.error && <p>Error: {state.error}</p>}
{state.data && <p>Data: {JSON.stringify(state.data)}</p>}
</div>
);
}
0x03 Context API
基础 Context
import { createContext, useContext, useState } from 'react';
// 创建 Context
interface AuthContextType {
user: { name: string; email: string } | null;
login: (credentials: any) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
// Provider 组件
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<{ name: string; email: string } | null>(null);
const login = async (credentials: any) => {
const user = await api.login(credentials);
setUser(user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// 自定义 Hook
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// 使用
function LoginButton() {
const { user, login, logout } = useAuth();
if (user) {
return <button onClick={logout}>Logout {user.name}</button>;
}
return (
<button onClick={() => login({ username: 'test', password: '123' })}>
Login
</button>
);
}
分离 Context(优化性能)
// 分离 State 和 Dispatch Context
const TasksContext = createContext<Task[] | null>(null);
const TasksDispatchContext = createContext<React.Dispatch<Action> | null>(null);
export function TasksProvider({ children }: { children: React.ReactNode }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
// 使用分离的 Context
function TaskList() {
const tasks = useContext(TasksContext);
const dispatch = useContext(TasksDispatchContext);
// 只有 tasks 变化时重渲染
return (
<ul>
{tasks?.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={() => dispatch({ type: 'toggle', id: task.id })}
/>
))}
</ul>
);
}
0x04 状态管理模式
复合 Context Provider
// 完整的状态管理示例
interface AppState {
user: User | null;
theme: 'light' | 'dark';
notifications: Notification[];
}
interface User {
id: string;
name: string;
}
interface Notification {
id: string;
message: string;
type: 'info' | 'success' | 'error';
}
type AppAction =
| { type: 'SET_USER'; user: User | null }
| { type: 'SET_THEME'; theme: 'light' | 'dark' }
| { type: 'ADD_NOTIFICATION'; notification: Notification }
| { type: 'REMOVE_NOTIFICATION'; id: string };
function appReducer(state: AppState, action: AppAction): AppState {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.user };
case 'SET_THEME':
return { ...state, theme: action.theme };
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [...state.notifications, action.notification]
};
case 'REMOVE_NOTIFICATION':
return {
...state,
notifications: state.notifications.filter(n => n.id !== action.id)
};
default:
return state;
}
}
// 创建 Provider
const AppStateContext = createContext<AppState | null>(null);
const AppDispatchContext = createContext<React.Dispatch<AppAction> | null>(null);
export function AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(appReducer, {
user: null,
theme: 'light',
notifications: []
});
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
// 自定义 Hooks
export function useAppState() {
const context = useContext(AppStateContext);
if (!context) {
throw new Error('useAppState must be used within AppProvider');
}
return context;
}
export function useAppDispatch() {
const context = useContext(AppDispatchContext);
if (!context) {
throw new Error('useAppDispatch must be used within AppProvider');
}
return context;
}
// 使用
function ThemeToggle() {
const { theme } = useAppState();
const dispatch = useAppDispatch();
return (
<button onClick={() =>
dispatch({
type: 'SET_THEME',
theme: theme === 'light' ? 'dark' : 'light'
})
}>
Toggle Theme
</button>
);
}
原子化状态(Atoms)
类似 Zustand 的原子化状态模式:
// atom.ts - 原子化状态
import { createContext, useContext, useState, useCallback } from 'react';
type Listener = () => void;
class Atom<T> {
private value: T;
private listeners: Set<Listener> = new Set();
constructor(initialValue: T) {
this.value = initialValue;
}
get(): T {
return this.value;
}
set(value: T): void {
this.value = value;
this.listeners.forEach(listener => listener());
}
subscribe(listener: Listener): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
}
function atom<T>(initialValue: T): Atom<T> {
return new Atom(initialValue);
}
// 创建 atoms
const userAtom = atom<User | null>(null);
const themeAtom = atom<'light' | 'dark'>('light');
const cartAtom = atom<CartItem[]>([]);
// Store 上下文
interface StoreValue {
userAtom: Atom<User | null>;
themeAtom: Atom<'light' | 'dark'>;
cartAtom: Atom<CartItem[]>;
}
const StoreContext = createContext<StoreValue | null>(null);
export function StoreProvider({ children }: { children: React.ReactNode }) {
return (
<StoreContext.Provider value={{
userAtom,
themeAtom,
cartAtom
}}>
{children}
</StoreContext.Provider>
);
}
// useAtom hook
function useAtom<T>(atom: Atom<T>): [T, (value: T) => void] {
const store = useContext(StoreContext);
if (!store) {
throw new Error('useAtom must be used within StoreProvider');
}
const [value, setValue] = useState(() => atom.get());
useEffect(() => {
return atom.subscribe(() => {
setValue(atom.get());
});
}, [atom]);
return [value, atom.set.bind(atom)];
}
// 使用
function UserProfile() {
const [user, setUser] = useAtom(userAtom);
return user ? (
<p>Welcome, {user.name}</p>
) : (
<button onClick={() => setUser({ id: '1', name: 'John' })}>
Login
</button>
);
}
function Cart() {
const [items, setItems] = useAtom(cartAtom);
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
0x05 外部状态管理库
Zustand
import { create } from 'zustand';
interface User {
id: string;
name: string;
}
interface CartItem {
id: string;
name: string;
price: number;
}
interface Store {
user: User | null;
theme: 'light' | 'dark';
cart: CartItem[];
// Actions
setUser: (user: User | null) => void;
toggleTheme: () => void;
addToCart: (item: CartItem) => void;
removeFromCart: (id: string) => void;
clearCart: () => void;
}
const useStore = create<Store>((set) => ({
user: null,
theme: 'light',
cart: [],
setUser: (user) => set({ user }),
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light'
})),
addToCart: (item) => set((state) => ({
cart: [...state.cart, item]
})),
removeFromCart: (id) => set((state) => ({
cart: state.cart.filter(item => item.id !== id)
})),
clearCart: () => set({ cart: [] })
}));
// 选择性订阅
function CartBadge() {
const count = useStore((state) => state.cart.length);
return <span>Cart: {count}</span>;
}
// 使用
function App() {
const { user, theme, cart, toggleTheme, addToCart } = useStore();
return (
<div className={theme}>
<CartBadge />
<button onClick={toggleTheme}>Toggle Theme</button>
<button onClick={() => addToCart({
id: '1',
name: 'Product',
price: 100
})}>
Add to Cart
</button>
</div>
);
}
Jotai
import { atom, useAtom } from 'jotai';
// 基础原子
const countAtom = atom(0);
const userAtom = atom<{ name: string } | null>(null);
// 派生原子(计算值)
const doubledCountAtom = atom((get) => get(countAtom) * 2);
// 异步原子
const dataAtom = atom(async () => {
const response = await fetch('/api/data');
return response.json();
});
// 带写入的原子
const persistentCountAtom = atom(
(get) => get(countAtom),
(get, set, newValue: number) => {
set(countAtom, newValue);
localStorage.setItem('count', String(newValue));
}
);
// 使用
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubled] = useAtom(doubledCountAtom);
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// 异步数据
function DataDisplay() {
const [data] = useAtom(dataAtom);
if (!data) return <p>Loading...</p>;
return <p>Data: {JSON.stringify(data)}</p>;
}
Redux Toolkit
import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
// Slice 定义
interface User {
id: string;
name: string;
}
interface UserState {
currentUser: User | null;
loading: boolean;
error: string | null;
}
const userSlice = createSlice({
name: 'user',
initialState: {
currentUser: null,
loading: false,
error: null
} as UserState,
reducers: {
setUser: (state, action: PayloadAction<User | null>) => {
state.currentUser = action.payload;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
}
}
});
const { setUser, setLoading, setError } = userSlice.actions;
// Store 配置
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
// React Hooks
const useAppSelector = <T>(selector: (state: RootState) => T): T => {
return selector(store.getState());
};
const useAppDispatch = () => store.dispatch;
// 使用
function UserDisplay() {
const { currentUser, loading, error } = useAppSelector(
(state) => state.user
);
const dispatch = useAppDispatch();
const login = () => {
dispatch(setLoading(true));
api.login().then(user => {
dispatch(setUser(user));
dispatch(setLoading(false));
}).catch(err => {
dispatch(setError(err.message));
dispatch(setLoading(false));
});
};
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return currentUser ? (
<p>Welcome, {currentUser.name}</p>
) : (
<button onClick={login}>Login</button>
);
}
0x06 状态管理选择指南
| 场景 | 推荐方案 |
|---|---|
| 简单组件内状态 | useState |
| 复杂状态逻辑 | useReducer |
| 跨组件共享状态 | Context API |
| 中小型应用 | Zustand / Jotai |
| 大型企业应用 | Redux Toolkit |
| 服务器状态 | React Query / SWR |
0x07 最佳实践
状态就近原则
// ❌ 错误:状态提升过高
function Parent() {
const [item, setItem] = useState('');
return (
<div>
<Input value={item} onChange={setItem} />
<Display value={item} />
</div>
);
}
// ✅ 正确:状态放在需要的地方
function Parent() {
return (
<div>
<Input />
<Display />
</div>
);
}
function Input() {
const [item, setItem] = useState('');
return <input value={item} onChange={e => setItem(e.target.value)} />;
}
function Display() {
return <DisplayContent />; // 从 Context 获取需要的共享状态
}
状态不可变性
// ❌ 错误:直接修改状态
function BadComponent() {
const [items, setItems] = useState<string[]>([]);
const addItem = (item: string) => {
items.push(item); // 直接修改数组
setItems(items);
};
}
// ✅ 正确:创建新的引用
function GoodComponent() {
const [items, setItems] = useState<string[]>([]);
const addItem = (item: string) => {
setItems([...items, item]); // 创建新数组
};
}
// 对象同样适用
function GoodObjectComponent() {
const [user, setUser] = useState({ name: 'John', age: 30 });
const updateAge = (newAge: number) => {
setUser({ ...user, age: newAge }); // 创建新对象
};
}
派生状态
function DerivedState() {
const [items, setItems] = useState([
{ id: 1, name: 'Apple', price: 1 },
{ id: 2, name: 'Banana', price: 2 }
]);
// ✅ 正确:从现有状态派生,不需要独立状态
const total = items.reduce((sum, item) => sum + item.price, 0);
const count = items.length;
return (
<div>
<p>Total items: {count}</p>
<p>Total price: ${total}</p>
</div>
);
}
参考
目录