You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React新手咨询:使用多useReducer处理同一份Todo数据是否合理

拆分Reducer是完全正确的做法!

你这个思路非常棒——把通用的数据获取逻辑和Todo业务操作拆分到不同的Reducer里,既符合单一职责原则,又能让dataFetchReducer真正实现复用,这正是React Hooks设计的初衷之一。下面我给你两种具体的实现方案,你可以根据自己的习惯选择:

方案一:组合Reducer(类似Redux的combineReducers)

这种方式适合希望保持单一状态树和单一dispatch的场景,RootReducer会根据Action类型自动分发给对应的子Reducer处理。

1. 编写Todo业务Reducer

先实现处理Todo操作的todoReducer,专注于reset、add、remove、markAsDone这些业务逻辑:

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'TODO_RESET':
      return { ...state, data: [] };
    case 'TODO_ADD':
      return { ...state, data: [...state.data, action.payload] };
    case 'TODO_REMOVE':
      return {
        ...state,
        data: state.data.filter(todo => todo.id !== action.payload.id)
      };
    case 'TODO_MARK_AS_DONE':
      return {
        ...state,
        data: state.data.map(todo =>
          todo.id === action.payload.id ? { ...todo, done: true } : todo
        )
      };
    default:
      // 如果不是Todo相关的Action,直接返回原状态
      return state;
  }
};

2. 组合RootReducer

创建一个RootReducer,把dataFetchReducer和todoReducer组合起来。它会先让dataFetchReducer处理数据获取相关的Action(比如FETCH_INIT/SUCCESS/FAILURE),再判断是否是Todo相关的Action,交给todoReducer处理:

const rootReducer = (state, action) => {
  // 先处理数据获取的Action
  const fetchUpdatedState = dataFetchReducer(state, action);
  
  // 如果是Todo业务Action,交给todoReducer处理
  if (action.type.startsWith('TODO_')) {
    return todoReducer(fetchUpdatedState, action);
  }
  
  return fetchUpdatedState;
};

3. 使用RootReducer初始化状态

现在只需要用RootReducer来创建状态和dispatch,所有Action都通过这个dispatch发送即可:

const initialData = { isLoading: true, isError: false, data: [] };
const [todoState, dispatch] = useReducer(rootReducer, initialData);

比如你要添加一个Todo,直接调用:

dispatch({ type: 'TODO_ADD', payload: { id: 1, text: 'Learn useReducer', done: false } });

获取数据的时候还是用原来的FETCH_系列Action,完全不影响dataFetchReducer的复用。


方案二:使用多个useReducer分离状态

如果希望把「数据获取元状态(loading/error)」和「Todo业务数据」完全分开管理,可以用两个独立的useReducer,分别对应不同的状态和dispatch(变量名区分开就不会冲突)。

1. 拆分状态并初始化两个Reducer

// 专门管理数据获取的状态(loading/error)
const initialFetchState = { isLoading: true, isError: false };
const [fetchState, fetchDispatch] = useReducer(dataFetchReducer, initialFetchState);

// 专门管理Todo业务数据
const initialTodoData = [];
const [todoData, todoDispatch] = useReducer(todoReducer, initialTodoData);

2. 同步Fetch到的Todo数据

在数据获取成功后,除了更新fetch状态,还要把数据同步到Todo的状态里(需要给todoReducer加一个TODO_SET_DATA的Action):

// 在useEffect里发起请求
useEffect(() => {
  fetchDispatch({ type: 'FETCH_INIT' });
  
  fetch('/api/todos')
    .then(res => res.json())
    .then(data => {
      fetchDispatch({ type: 'FETCH_SUCCESS', payload: data });
      // 同步数据到Todo状态
      todoDispatch({ type: 'TODO_SET_DATA', payload: data });
    })
    .catch(() => {
      fetchDispatch({ type: 'FETCH_FAILURE' });
    });
}, []);

对应的todoReducer要加上这个Action的处理:

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'TODO_SET_DATA':
      return action.payload;
    // 其他Todo操作的case...
    default:
      return state;
  }
};

这种方式的好处是状态边界更清晰,每个Reducer只关注自己的逻辑,dataFetchReducer同样可以直接拿到其他组件里复用。


两种方案怎么选?

  • 如果你的组件需要把loading/error状态和Todo数据一起渲染(比如加载中时显示骨架屏),方案一更方便,因为状态是统一的;
  • 如果想让状态完全解耦,或者未来Todo数据可能有更复杂的独立逻辑,方案二会更灵活。

不管选哪种,你的核心思路——拆分Reducer实现复用和职责分离——都是完全正确的,继续保持这个好习惯!

内容的提问来源于stack exchange,提问作者dinker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 09:22:41