React 状态管理方案对比
1. useContext + useReducer
基本使用
// 创建 Context
const CounterContext = createContext<{
count: number;
dispatch: React.Dispatch<any>;
} | null>(null);
// 定义 reducer
function counterReducer(state: { count: number }, action: { type: string }) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
// Provider 组件
function CounterProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<CounterContext.Provider value={{ count: state.count, dispatch }}>
{children}
</CounterContext.Provider>
);
}
// 使用状态
function Counter() {
const counter = useContext(CounterContext);
if (!counter) throw new Error('Counter must be used within CounterProvider');
return (
<div>
Count: {counter.count}
<button onClick={() => counter.dispatch({ type: 'increment' })}>+</button>
</div>
);
}
优点
- React 原生支持,无需额外依赖
- 适合小型应用或组件树局部状态管理
- 使用简单,上手容易
- 与 React 结合紧密
缺点
- 可能导致不必要的重渲染
- 状态更新可能导致整个子树重新渲染
- 不适合大型应用或复杂状态管理
- 缺乏开发者工具支持
2. Redux
基本使用
// 创建 slice
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
state.value -= 1;
}
}
});
// 创建 store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 使用状态
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
Count: {count}
<button onClick={() => dispatch(counterSlice.actions.increment())}>+</button>
</div>
);
}
优点
- 完善的生态系统和工具支持
- 强大的中间件系统
- 严格的单向数据流
- 适合大型应用
- 优秀的开发者工具
- 可预测的状态更新
缺点
- 配置相对复杂
- 模板代码较多
- 学习曲线陡峭
- 小型应用可能过于重量级
- 状态更新需要遵循不可变性
3. Zustand
基本使用
import create from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
}
const useStore = create<CounterState>((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 }))
}));
function Counter() {
const { count, increment } = useStore();
return (
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
);
}
优点
- API 简单直观
- 体积小巧
- 支持 React 之外的场景
- 良好的 TypeScript 支持
- 无需 Provider 包裹
- 自动处理状态更新优化
缺点
- 生态系统相对较小
- 开发者工具支持有限
- 大型应用可能需要额外的状态组织方案
对比总结
适用场景
-
useContext + useReducer
- 小型应用
- 组件树局部状态管理
- 简单的状态逻辑
- 需要与 React 紧密集成
-
Redux
- 大型应用
- 复杂的状态逻辑
- 需要强大的开发者工具
- 需要严格的状态管理规范
- 团队协作项目
-
Zustand
- 中小型应用
- 需要简单但强大的状态管理
- 需要良好的性能
- 不想配置太多模板代码
性能对比
-
useContext + useReducer
- 可能导致不必要的重渲染
- 需要手动优化性能
- 适合小规模状态更新
-
Redux
- 内置性能优化
- 选择器机制避免不必要的重渲染
- 大规模状态更新性能好
-
Zustand
- 自动批量更新
- 内置性能优化
- 适合频繁的状态更新
开发体验
-
useContext + useReducer
- 学习成本低
- 代码量少
- 调试相对困难
-
Redux
- 完善的开发者工具
- 清晰的状态追踪
- 代码组织规范
-
Zustand
- 简单直观
- 配置少
- 快速上手
实际场景对比
1. 异步数据获取
useContext + useReducer:
// 定义 actions 和 reducer
type State = { data: any[]; loading: boolean; error: string | null };
type Action =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: any[] }
| { type: 'FETCH_ERROR'; payload: string };
const DataContext = createContext<{
state: State;
dispatch: React.Dispatch<Action>;
} | null>(null);
// 使用示例
function DataList() {
const { state, dispatch } = useContext(DataContext)!;
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
fetchData();
}, []);
if (state.loading) return <div>Loading...</div>;
if (state.error) return <div>Error: {state.error}</div>;
return <div>{state.data.map(item => <Item key={item.id} {...item} />)}</div>;
}
Redux (with Redux Toolkit):
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
const fetchData = createAsyncThunk('data/fetch', async () => {
const response = await fetch('/api/data');
return response.json();
});
const dataSlice = createSlice({
name: 'data',
initialState: { data: [], loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {
state.loading = true;
})
.addCase(fetchData.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchData.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
// 使用示例
function DataList() {
const { data, loading, error } = useSelector(state => state.data);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchData());
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
}
Zustand:
import create from 'zustand';
interface DataState {
data: any[];
loading: boolean;
error: string | null;
fetchData: () => Promise<void>;
}
const useStore = create<DataState>((set) => ({
data: [],
loading: false,
error: null,
fetchData: async () => {
set({ loading: true });
try {
const response = await fetch('/api/data');
const data = await response.json();
set({ data, loading: false, error: null });
} catch (error) {
set({ error: error.message, loading: false });
}
},
}));
// 使用示例
function DataList() {
const { data, loading, error, fetchData } = useStore();
useEffect(() => {
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
}
2. 表单状态管理
useContext + useReducer:
type FormState = {
values: { [key: string]: string };
errors: { [key: string]: string };
touched: { [key: string]: boolean };
};
type FormAction =
| { type: 'SET_FIELD_VALUE'; field: string; value: string }
| { type: 'SET_FIELD_ERROR'; field: string; error: string }
| { type: 'SET_FIELD_TOUCHED'; field: string };
// 使用示例
function Form() {
const { state, dispatch } = useContext(FormContext)!;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch({
type: 'SET_FIELD_VALUE',
field: e.target.name,
value: e.target.value,
});
};
return (
<form>
<input
name="email"
value={state.values.email}
onChange={handleChange}
/>
{state.errors.email && <span>{state.errors.email}</span>}
</form>
);
}
Redux:
const formSlice = createSlice({
name: 'form',
initialState: {
values: {},
errors: {},
touched: {},
},
reducers: {
setFieldValue: (state, action) => {
state.values[action.payload.field] = action.payload.value;
},
setFieldError: (state, action) => {
state.errors[action.payload.field] = action.payload.error;
},
setFieldTouched: (state, action) => {
state.touched[action.payload.field] = true;
},
},
});
// 使用示例
function Form() {
const form = useSelector(state => state.form);
const dispatch = useDispatch();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(formSlice.actions.setFieldValue({
field: e.target.name,
value: e.target.value,
}));
};
return (
<form>
<input
name="email"
value={form.values.email}
onChange={handleChange}
/>
{form.errors.email && <span>{form.errors.email}</span>}
</form>
);
}
Zustand:
interface FormState {
values: { [key: string]: string };
errors: { [key: string]: string };
touched: { [key: string]: boolean };
setFieldValue: (field: string, value: string) => void;
setFieldError: (field: string, error: string) => void;
setFieldTouched: (field: string) => void;
}
const useFormStore = create<FormState>((set) => ({
values: {},
errors: {},
touched: {},
setFieldValue: (field, value) =>
set(state => ({
values: { ...state.values, [field]: value },
})),
setFieldError: (field, error) =>
set(state => ({
errors: { ...state.errors, [field]: error },
})),
setFieldTouched: (field) =>
set(state => ({
touched: { ...state.touched, [field]: true },
})),
}));
// 使用示例
function Form() {
const { values, errors, setFieldValue } = useFormStore();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFieldValue(e.target.name, e.target.value);
};
return (
<form>
<input
name="email"
value={values.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
</form>
);
}
性能对比分析
1. 大规模数据更新
-
useContext + useReducer
- 每次状态更新都会触发所有消费组件重新渲染
- 需要手动实现 memoization 来优化性能
- 适合小规模数据和简单更新
-
Redux
- 内置选择器机制,只更新必要的组件
- 支持数据规范化和缓存
- 适合复杂的数据关系和频繁更新
-
Zustand
- 自动比较更新,避免不必要的渲染
- 支持选择性订阅状态
- 适合中等规模的数据更新
2. 开发工具支持
-
useContext + useReducer
- React DevTools 基础支持
- 无专门的调试工具
- 状态变化追踪较困难
-
Redux
- Redux DevTools 提供完整的状态追踪
- 时间旅行调试
- 状态快照和导入/导出
-
Zustand
- 支持 Redux DevTools
- 基本的状态追踪功能
- 相比 Redux 功能较简单
3. 代码量对比
- useContext + useReducer: 最少的样板代码,但可能需要更多的手动优化代码
- Redux: 较多的样板代码,但有完整的工具和模式支持
- Zustand: 最简洁的 API,适中的代码量