React 状态管理
一、状态管理概述
1.1 为什么需要状态管理
- 组件间共享状态
- 避免 Prop Drilling
- 复杂应用的状态逻辑集中管理
- 状态持久化、时间旅行等高级功能
1.2 方案对比
| 方案 | 复杂度 | 适用场景 |
|---|---|---|
| useState/useReducer | 低 | 局部状态 |
| Context | 低 | 简单共享状态 |
| Zustand | 低 | 中小型应用 |
| Jotai/Recoil | 中 | 原子化状态 |
| Redux Toolkit | 中高 | 大型应用 |
二、React 内置方案
2.1 useReducer
javascript
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}2.2 Context + useReducer
javascript
// 创建 Context
const StateContext = createContext();
const DispatchContext = createContext();
// Provider 组件
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 自定义 Hook
function useAppState() {
return useContext(StateContext);
}
function useAppDispatch() {
return useContext(DispatchContext);
}
// 使用
function Counter() {
const state = useAppState();
const dispatch = useAppDispatch();
// ...
}2.3 Context 性能问题
javascript
// ❌ 问题:任何 state 变化都会触发所有消费者重渲染
const AppContext = createContext({ user: null, theme: 'light' });
// ✅ 解决方案 1:拆分 Context
const UserContext = createContext(null);
const ThemeContext = createContext('light');
// ✅ 解决方案 2:useMemo 包裹 value
function Provider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// ✅ 解决方案 3:使用 use-context-selector
import { createContext, useContextSelector } from 'use-context-selector';
const count = useContextSelector(AppContext, (v) => v.count);三、Zustand
3.1 基本使用
javascript
import { create } from 'zustand';
// 创建 Store
const useStore = create((set, get) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
// 访问其他状态
doubleCount: () => get().count * 2
}));
// 使用
function Counter() {
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return (
<button onClick={increment}>{count}</button>
);
}3.2 异步操作
javascript
const useStore = create((set) => ({
users: [],
loading: false,
error: null,
fetchUsers: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/users');
const users = await response.json();
set({ users, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
}
}));3.3 中间件
javascript
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
const useStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}),
{
name: 'counter-storage'
}
)
)
);3.4 Slice 模式
javascript
// 拆分 Store
const createUserSlice = (set) => ({
user: null,
setUser: (user) => set({ user })
});
const createCartSlice = (set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] }))
});
// 合并
const useStore = create((...a) => ({
...createUserSlice(...a),
...createCartSlice(...a)
}));四、Redux Toolkit
4.1 创建 Slice
javascript
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // Immer 允许直接修改
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;4.2 配置 Store
javascript
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import userReducer from './userSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
user: userReducer
}
});
// 类型推断
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;4.3 React 集成
javascript
import { Provider, useSelector, useDispatch } from 'react-redux';
// 根组件
<Provider store={store}>
<App />
</Provider>
// 使用
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(increment())}>
{count}
</button>
);
}4.4 异步操作 (createAsyncThunk)
javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async (_, { rejectWithValue }) => {
try {
const response = await fetch('/api/users');
return response.json();
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { list: [], loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.list = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
});
}
});五、Jotai (原子化状态)
5.1 基本使用
javascript
import { atom, useAtom } from 'jotai';
// 创建原子
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
// 可写派生原子
const countWithIncrement = atom(
(get) => get(countAtom),
(get, set, update) => {
set(countAtom, get(countAtom) + update);
}
);
// 使用
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubleCount] = useAtom(doubleCountAtom);
return (
<>
<p>{count} x 2 = {doubleCount}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
</>
);
}5.2 异步原子
javascript
const userAtom = atom(async () => {
const response = await fetch('/api/user');
return response.json();
});
function UserProfile() {
const [user] = useAtom(userAtom);
// user 会在数据加载完成后更新
}六、高频面试题
Q1: Context 的性能问题如何解决?
- 拆分多个 Context
- useMemo 包裹 Provider value
- 使用 use-context-selector
- 考虑使用 Zustand 等状态库
Q2: Redux 和 Zustand 的区别?
| 对比项 | Redux | Zustand |
|---|---|---|
| 样板代码 | 多 | 少 |
| 学习曲线 | 陡 | 平缓 |
| 中间件 | 丰富 | 基础 |
| 适用场景 | 大型应用 | 中小应用 |
Q3: 什么时候需要状态管理库?
- 多组件需要共享状态
- 状态逻辑复杂
- 需要持久化、时间旅行等功能
- Context 性能不满足需求