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

Redux todo应用中remainingTasks/updateRemainingTasks action触发两次问题咨询

这是代码写法问题导致的异常现象,而非正常逻辑。

根因分析

你直接在RemainingTasks组件的顶层执行dispatch(updateRemainingTasks(incompleteTasks)),而React组件每次渲染时都会执行函数体内所有顶层代码,因此会形成如下触发循环:

  1. 你执行添加/切换任务完成状态/删除未完成任务操作时,首先会修改taskSlice的状态
  2. RemainingTasks组件订阅了taskSlice,因此触发第一次渲染,执行顶层的dispatch,更新remainingTasks状态
  3. 组件同时订阅了remainingTasks状态,状态变更后触发第二次渲染,再次执行dispatch,若此时计算出的incompleteTasks和上一次payload相同,Redux状态无更新,循环终止,因此你会看到两次action触发
  4. 当你删除已完成任务时,计算出的incompleteTasks和原有remainingTasks值完全一致,第一次dispatch后Redux状态没有变化,不会触发第二次渲染,因此仅能看到一次action触发

优化方案

最优方案:移除冗余的remainingTasks状态

剩余待办数完全是从任务列表派生出来的状态,没有必要单独存储,直接删除对应的slice,组件里直接使用计算出的incompleteTasks渲染即可,无需任何dispatch操作,修改后的组件代码如下:

import { useSelector } from "react-redux";
import { selectTaskSlice } from "../task/taskSlice";

const RemainingTasks = () => {
  const taskSlice = useSelector(selectTaskSlice);
  // 直接计算未完成任务数渲染即可
  const incompleteTasks = taskSlice.filter((task) => !task.completed).length;

  return (
    <div>
      <h1 className="header">
        {incompleteTasks > 1
          ? `${incompleteTasks} Tasks Left`
          : `${incompleteTasks} Task Left`}
      </h1>
    </div>
  );
};

export default RemainingTasks;

兼容方案:将dispatch放到副作用中执行

如果你因特殊需求必须保留独立的remainingTasks状态,需要把dispatch操作放到useEffect中,仅当未完成任务数变化时才触发更新,避免每次渲染都执行dispatch:

import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import {
  selectRemainingTasksSlice,
  updateRemainingTasks,
} from "./remainingTasksSlice";
import { selectTaskSlice } from "../task/taskSlice";

const RemainingTasks = () => {
  const dispatch = useDispatch();
  const remainingTasksSlice = useSelector(selectRemainingTasksSlice);
  const taskSlice = useSelector(selectTaskSlice);

  const incompleteTasks = taskSlice.filter((task) => !task.completed).length;

  // 仅当incompleteTasks变化时才更新状态
  useEffect(() => {
    dispatch(updateRemainingTasks(incompleteTasks));
  }, [dispatch, incompleteTasks])

  return (
    <div>
      <h1 className="header">
        {remainingTasksSlice > 1
          ? `${remainingTasksSlice} Tasks Left`
          : `${remainingTasksSlice} Task Left`}
      </h1>
    </div>
  );
};

export default RemainingTasks;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 05:36:03