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

Redux Store无法触发React函数组件重渲染问题求助

问题描述

使用React函数组件时遇到Redux Store更新后组件不重渲染的问题。Redux DevTools显示状态流转正常且已正确更新状态,但App组件未按预期重渲染。以下是相关代码:

App.js

import Habbit from "./Habbit";
import NavBar from "./NavBar";
import HabitTrackerWeekView from "./HabitTrackerWeekView";
import { switchComponents, addHabit } from "../Action";
import { useEffect, useState } from "react";

function App({ store }) {
  const [newHabit, setNewHabit] = useState("");
  const { habitList, switchCom, currentMonth, doneCount } = store.getState();
  useEffect(() => {
    console.log("app.js done count ", doneCount);
  }, [doneCount]);


  return (
    <div className="App">
      <NavBar />
      <HabitTrackerWeekView
        habits={store.getState()}
        dispatch={store.dispatch}
        currentMonth={currentMonth}
      />
    </div>
  );
}

export default App;

HabitTrackerWeekView.js

import React, { useEffect, useReducer, useState } from "react";
import { countDoneStatuses } from "../Action";
import {
  format,
  subWeeks,
  startOfWeek,
  endOfWeek,
  eachDayOfInterval,
  addWeeks,
} from "date-fns";

function HabitTrackerWeekView({ habits, dispatch }) {
  const { habitList } = habits;
  const [currentMonth, setCurrentMonth] = useState(new Date());
  const [habitStatuses, setHabitStatuses] = useState({});
  const [habitDoneCounts, setHabitDoneCounts] = useState(
    habitList.reduce((counts, habit) => ({ ...counts, [habit]: 0 }), {})
  );
  useEffect(() => {
    // console.log("Current habitDoneCounts:", habitDoneCounts);
    dispatch(countDoneStatuses(habitDoneCounts));
  }, [habitDoneCounts]);

  const weekdays = [
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
  ];
  const getCurrentWeekDates = () => {
    const startOfCurrentWeek = startOfWeek(currentMonth, { weekStartsOn: 1 });
    const endOfCurrentWeek = endOfWeek(currentMonth, { weekStartsOn: 1 });
    const datesOfCurrentWeek = eachDayOfInterval({
      start: startOfCurrentWeek,
      end: endOfCurrentWeek,
    });
    const formattedDatesOfCurrentWeek = datesOfCurrentWeek.map((date) =>
      format(date, "dd")
    );

    return formattedDatesOfCurrentWeek;
  };
  const dates = getCurrentWeekDates();
  const handlePreviousWeek = () => {
    setCurrentMonth(subWeeks(currentMonth, 1));
  };

  const getMonthYear = () => {
    const dateFormat = "MMM yyyy";
    return format(currentMonth, dateFormat);
  };

  const handleNextWeek = () => {
    setCurrentMonth(addWeeks(currentMonth, 1));
  };

  const handleTodaysStatus = (date, habit) => {
    const key = `${date}-${habit}`;
    setHabitStatuses((prevStatuses) => {
      const currentStatus = prevStatuses[key] || "none";

      const newStatus =
        currentStatus === "none"
          ? "done"
          : currentStatus === "done"
          ? "not done"
          : "none";
      setHabitDoneCounts((prevCounts) => ({
      ...prevCounts,
      [habit]:
        newStatus === "done"
          ? prevCounts[habit] + 1
          : Math.max(0, prevCounts[habit] - 1),
    }));
      return { ...prevStatuses, [key]: newStatus };
    });
    // console.log(habitStatuses);
  };

  return (
    <div className="habit-tracker-week-view">
      <div className="months">
        <div className="previous-month" onClick={handlePreviousWeek}>
          <img
            width="24"
            height="24"
            src="https://img.icons8.com/material-outlined/24/back--v1.png"
            alt="back--v1"
          />
        </div>
        <div className="month">{getMonthYear()}</div>
        <div className="next-month" onClick={handleNextWeek}>
          <img
            width="24"
            height="24"
            src="https://img.icons8.com/material-outlined/24/forward.png"
            alt="forward"
          />
        </div>
      </div>
      <div className="day-of-the-week">
        {weekdays.map((day) => (
          <label>{day}</label>
        ))}
      </div>
      {/* <h1>Habit Tracker</h1> */}
      <div className="habits">
        {habitList.map((habit) => (
          <>
            <div className="habit-info">
              <div className="habit-name">{habit}</div>
              <div className="habit-time">time</div>
            </div>
            <div className="habit-dates">
              {dates.map((date) => (
                <div
                  className={`habit-date ${
                    habitStatuses[`${date}-${habit}`] === "done"
                      ? "done"
                      : `${
                          habitStatuses[`${date}-${habit}`] === "not done"
                            ? "not-done"
                            : ""
                        }`
                  }`}
                  onClick={() => handleTodaysStatus(date, habit)}
                >
                  {date}
                </div>
              ))}
            </div>
          </>
        ))}
      </div>
    </div>
  );
}
export default HabitTrackerWeekView;
解决方案

核心问题是App组件没有订阅Redux Store的状态变化。直接调用store.getState()只会在组件初始渲染时获取一次状态,后续Store更新时不会自动触发组件重渲染。

方法1:使用react-redux的useSelector和useDispatch(推荐)

这是React Redux官方推荐的方式,会自动订阅状态变化,当选中的状态更新时触发组件重渲染。

修改App.js:

import Habbit from "./Habbit";
import NavBar from "./NavBar";
import HabitTrackerWeekView from "./HabitTrackerWeekView";
import { switchComponents, addHabit } from "../Action";
import { useEffect, useState } from "react";
// 导入useSelector和useDispatch
import { useSelector, useDispatch } from "react-redux";

function App() { // 不再需要传入store
  const [newHabit, setNewHabit] = useState("");
  // 使用useSelector获取状态
  const { habitList, switchCom, currentMonth, doneCount } = useSelector(state => state);
  const dispatch = useDispatch(); // 获取dispatch函数

  useEffect(() => {
    console.log("app.js done count ", doneCount);
  }, [doneCount]);


  return (
    <div className="App">
      <NavBar />
      <HabitTrackerWeekView
        habits={useSelector(state => state)} // 同样用useSelector传递状态
        dispatch={dispatch}
        currentMonth={currentMonth}
      />
    </div>
  );
}

export default App;

同时确保根组件已用Provider包裹(示例index.js):

import { Provider } from 'react-redux';
import store from './store'; // 你的Redux store配置
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

方法2:手动订阅Store变化(不推荐,仅作了解)

若不想使用react-redux hooks,可手动订阅store的subscribe方法,状态变化时强制组件更新:

修改App.js:

import Habbit from "./Habbit";
import NavBar from "./NavBar";
import HabitTrackerWeekView from "./HabitTrackerWeekView";
import { switchComponents, addHabit } from "../Action";
import { useEffect, useState } from "react";

function App({ store }) {
  const [newHabit, setNewHabit] = useState("");
  // 用state保存当前状态,触发重渲染
  const [state, setState] = useState(store.getState());

  useEffect(() => {
    // 订阅store变化,每次更新时重新获取状态
    const unsubscribe = store.subscribe(() => {
      setState(store.getState());
    });

    // 组件卸载时取消订阅
    return () => unsubscribe();
  }, [store]);

  const { habitList, switchCom, currentMonth, doneCount } = state;

  useEffect(() => {
    console.log("app.js done count ", doneCount);
  }, [doneCount]);


  return (
    <div className="App">
      <NavBar />
      <HabitTrackerWeekView
        habits={state}
        dispatch={store.dispatch}
        currentMonth={currentMonth}
      />
    </div>
  );
}

export default App;

额外注意点

  • 传递habits时,建议仅传递组件需要的字段(如habitList),避免不必要的重渲染。
  • 确保Redux reducer是纯函数,返回新的状态对象/数组而非修改原有状态。若reducer直接修改原状态,即使DevTools显示更新,组件也可能不触发重渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 05:44:55