Skip to main content

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>
);
}

优点

  1. React 原生支持,无需额外依赖
  2. 适合小型应用或组件树局部状态管理
  3. 使用简单,上手容易
  4. 与 React 结合紧密

缺点

  1. 可能导致不必要的重渲染
  2. 状态更新可能导致整个子树重新渲染
  3. 不适合大型应用或复杂状态管理
  4. 缺乏开发者工具支持

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>
);
}

优点

  1. 完善的生态系统和工具支持
  2. 强大的中间件系统
  3. 严格的单向数据流
  4. 适合大型应用
  5. 优秀的开发者工具
  6. 可预测的状态更新

缺点

  1. 配置相对复杂
  2. 模板代码较多
  3. 学习曲线陡峭
  4. 小型应用可能过于重量级
  5. 状态更新需要遵循不可变性

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>
);
}

优点

  1. API 简单直观
  2. 体积小巧
  3. 支持 React 之外的场景
  4. 良好的 TypeScript 支持
  5. 无需 Provider 包裹
  6. 自动处理状态更新优化

缺点

  1. 生态系统相对较小
  2. 开发者工具支持有限
  3. 大型应用可能需要额外的状态组织方案

对比总结

适用场景

  1. useContext + useReducer

    • 小型应用
    • 组件树局部状态管理
    • 简单的状态逻辑
    • 需要与 React 紧密集成
  2. Redux

    • 大型应用
    • 复杂的状态逻辑
    • 需要强大的开发者工具
    • 需要严格的状态管理规范
    • 团队协作项目
  3. Zustand

    • 中小型应用
    • 需要简单但强大的状态管理
    • 需要良好的性能
    • 不想配置太多模板代码

性能对比

  1. useContext + useReducer

    • 可能导致不必要的重渲染
    • 需要手动优化性能
    • 适合小规模状态更新
  2. Redux

    • 内置性能优化
    • 选择器机制避免不必要的重渲染
    • 大规模状态更新性能好
  3. Zustand

    • 自动批量更新
    • 内置性能优化
    • 适合频繁的状态更新

开发体验

  1. useContext + useReducer

    • 学习成本低
    • 代码量少
    • 调试相对困难
  2. Redux

    • 完善的开发者工具
    • 清晰的状态追踪
    • 代码组织规范
  3. 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,适中的代码量