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

React中state存储的日期切换月份时无法正常更新渲染问题

问题根因

三个核心问题直接导致切换异常:

  • globalDate定义在组件函数顶层,每次组件重渲染都会重新执行const globalDate = new Date()生成新的日期实例,根本不是稳定的状态参考值,再加上直接修改这个可变对象,闭包很容易引用到旧值
  • React的state更新是异步批量执行的,你在updateStates里调用setMonth/setYear之后,立刻用当前作用域里的旧year/month值计算月初、月末相关数据,拿到的全是更新前的旧值,算出来的日期列表自然和当前选中的月份对不上
  • 存了大量冗余派生状态:月初星期几、月末日期、日期网格数组这些全是可以根据当前年、月直接算出来的值,单独存成state非常容易出现多个状态不同步的问题,再加上你的useEffect依赖是空数组,只会在组件首次加载时执行一次日期生成,后续状态变化不会自动触发重算
  • 反向切换跳两个月的问题,是开发环境React严格模式重复执行更新逻辑,叠加可变对象修改、闭包旧值问题共同导致的。
修复方案

直接砍掉所有冗余状态,只保留最核心的当前年、月两个状态,所有派生数据直接基于这两个值计算,不要单独存state;切换月份时用setState的函数式更新拿最新值,从根源上避免闭包问题。

修复后代码
const MonthView = () => {
    const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    const daysOfWeek = ["Sun", "Mon", "Tues", "Wed", "Thu", "Fri", "Sat"];

    // 只存核心状态:当前选中的年、月
    const [currentMonth, setCurrentMonth] = useState(new Date().getMonth());
    const [currentYear, setCurrentYear] = useState(new Date().getFullYear());
    const [events, setEvents] = useState([]);

    // 所有派生值直接基于年、月计算,不需要存为独立state
    const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay();
    const lastDayOfMonth = new Date(currentYear, currentMonth + 1, 0).getDay();
    const lastDayOfMonthNumber = new Date(currentYear, currentMonth + 1, 0).getDate();

    // 生成日历网格数据
    const days = (() => {
        const tempDays = [];
        // 填充月初空白占位
        for (let i = 0; i < firstDayOfMonth; i++) {
            tempDays.push(null);
        }
        // 填充当月日期
        for (let i = 1; i <= lastDayOfMonthNumber; i++) {
            tempDays.push(i);
        }
        // 填充月末空白占位
        for (let i = 0; i < (6 - lastDayOfMonth); i++) {
            tempDays.push(i+1);
        }
        return tempDays;
    })();

    // 切换上一月
    const handlePrevMonth = () => {
        setCurrentMonth(prev => {
            if (prev === 0) {
                setCurrentYear(y => y - 1);
                return 11;
            }
            return prev - 1;
        })
    }

    // 切换下一月
    const handleNextMonth = () => {
        setCurrentMonth(prev => {
            if (prev === 11) {
                setCurrentYear(y => y + 1);
                return 0;
            }
            return prev + 1;
        })
    }

    return(
        <>
        <Table striped bordered hover size="sm" variant="light">
            <thead>
                <tr>
                    <th></th>
                    <th></th>
                    <th><Button variant="info" onClick={handlePrevMonth}><FontAwesomeIcon icon={faAngleLeft} /></Button></th>
                    <th>{months[currentMonth]} {currentYear}</th>
                    <th><Button variant="info" onClick={handleNextMonth}><FontAwesomeIcon icon={faAngleRight} /></Button></th>
                    <th></th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    {daysOfWeek.map((day, index) => {
                        return <td key={index}>{day}</td>
                    })}
                </tr>
            
                {days.map((day, index) => {
                    if (index % 7 === 5) {
                        return <tr key={index}> {days.slice(index - 5, index + 2).map((day, index2) => {
                            return <td key={index2} onClick={() => {console.log(index + index2 - 5);}}>{day}</td>
                        })}
                        </tr>
                    }
                })}
            </tbody>
        </Table>
        </>
    )
}
额外优化点
  • 如果担心日期计算逻辑重复执行有性能损耗,可以给days这类派生值加useMemo包裹,依赖设为[currentMonth, currentYear]即可,只有年月变化时才重新计算
  • 后续做日期点击、事件挂载逻辑时,直接用currentYear/currentMonth加单元格的日期值生成完整Date对象就行,不需要额外存更多日期状态
  • 不需要自己维护daysInMonth数组硬编码天数,JS原生Date对象会自动处理大小月、闰年2月的天数问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 01:12:31