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

React日历组件切换年份时Hooks状态重置为默认值问题

问题:修改年月后日历组件状态重置为默认值

开发日历组件时,在MainShell中修改年月并提交后,Calendar组件内的年月状态会重置为默认值,使用e.preventDefault()也无法解决。

问题根源

  1. 子组件未同步父组件props更新:Calendar组件内部用useState(props.month)和useState(props.year)初始化状态,但useState的初始值仅在组件挂载时生效,后续父组件传入的props变化不会自动更新子组件的state。
  2. useEffect依赖项缺失:生成日历的useEffect依赖数组为空,仅在组件首次挂载时执行一次,后续年月变更不会触发日历重新计算。
  3. 代码存在逻辑bug:比如数组索引越界、闰年判断不完整、数值判断精度问题等,也会导致逻辑异常。

修复方案

核心修复:同步props变化并更新日历逻辑

因为Calendar组件的年月完全由父组件控制,直接使用父组件传入的props即可,移除子组件内部的年月状态,并给useEffect添加正确的依赖项,确保年月变化时重新生成日历。

修改后的Calendar组件代码

import { useEffect, useState } from "react";
import { Week } from "./week";

export function Calender(props) {
  // 直接使用父组件传入的props,移除内部状态
  const { month, year } = props;
  const [currentCalender, setCurrentCalender] = useState([]);

  const daysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  function dayOfWeek(month, year) {
    const yearOfFocus = year % 100;
    // 修复闰年判断:整百年需被400整除才是闰年
    const isLeapYear = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
    
    const monthCode = [
      [6, 5],
      [2, 1],
      [2],
      [5],
      [0],
      [3],
      [5],
      [1],
      [4],
      [6],
      [2],
      [4],
    ];
    let specificCode = -1;

    if ((month === 1 || month === 2) && isLeapYear) {
      specificCode = monthCode[month - 1][1];
    } else {
      specificCode = monthCode[month - 1][0];
    }

    let bonusYearCode = 0;
    // 修复年份世纪判断的精度问题
    const century = Math.floor(year / 100);
    if (century === 19) {
      bonusYearCode = 1;
    } else if (century === 21) {
      bonusYearCode = 5;
    }

    let yearCode = yearOfFocus + Math.trunc(yearOfFocus * 0.25);
    yearCode = (yearCode % 7) + bonusYearCode;

    return (specificCode + 1 + yearCode) % 7;
  }

  function firstWeek(day, week) {
    let lastMonthIndex = month - 2;
    let lastMonthDays;
    // 修复数组索引越界问题(原代码用12,数组索引为0-11)
    if (lastMonthIndex < 0) {
      lastMonthDays = daysOfMonth[11];
    } else {
      lastMonthDays = daysOfMonth[lastMonthIndex];
    }
    let digit = 0;
    for (let i = day - 1; i >= 0; i--) {
      week.unshift(lastMonthDays - digit);
      digit++;
    }
    return week;
  }

  function lastWeek(calender) {
    let nextDay = 1;
    let week = calender[calender.length - 1];
    for (let i = week.length; i < 7; i++) {
      week.push(nextDay);
      nextDay++;
    }
    return week;
  }

  function getCalender(day) {
    let calender = [];
    let week = [];
    if (day !== 1 && calender.length < 1) {
      week = firstWeek(day, week);
    }
    // 修复闰年2月天数问题
    let limit = daysOfMonth[month - 1];
    if (month === 2 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0)) {
      limit = 29;
    }
    for (let i = 0; i < limit; i++) {
      if (week.length === 7) {
        calender.push(week);
        week = [];
      }
      week.push(i + 1);
    }
    if (week.length > 0) {
      calender.push(week);
    }
    if (calender[calender.length - 1].length < 7) {
      week = lastWeek(calender);
      calender[calender.length - 1] = week;
    }
    setCurrentCalender(calender);
  }

  function getDay(e) {
    console.log(e);
  }

  // 添加month和year作为依赖,年月变化时重新生成日历
  useEffect(() => {
    const day = dayOfWeek(month, year);
    getCalender(day);
  }, [month, year]);

  return (
    <table id="calender">
      <thead>
        <tr id="calender header">
          <th>Sunday</th>
          <th>Monday</th>
          <th>Tuesday</th>
          <th>Wednesday</th>
          <th>Thursday</th>
          <th>Friday</th>
          <th>Saturday</th>
        </tr>
      </thead>
      <tbody>
        {currentCalender.map((e, i) => (
          <Week list={e} key={"week" + i} getDay={getDay} />
        ))}
      </tbody>
    </table>
  );
}

优化后的MainShell组件代码

添加输入限制和简单的参数校验,避免无效输入:

import { useState } from "react";
import { Calender } from "./calender";

export function MainShell(props) {
  const currentDate = new Date();
  const [month, setMonth] = useState(currentDate.getMonth() + 1);
  const [year, setYear] = useState(currentDate.getFullYear());
  const [thisMonth, setThisMonth] = useState(currentDate.getMonth() + 1);
  const [thisYear, setThisYear] = useState(currentDate.getFullYear());

  const setEvent = (event) => {
    event.preventDefault();
    const parsedMonth = parseInt(thisMonth);
    const parsedYear = parseInt(thisYear);
    // 校验输入的月份和年份是否合法
    if (parsedMonth >= 1 && parsedMonth <= 12 && parsedYear > 1900 && parsedYear < 2100) {
      setMonth(parsedMonth);
      setYear(parsedYear);
    }
  };

  return (
    <div>
      <Calender year={year} month={month} />
      <div>
        <form onSubmit={setEvent}>
          <div>
            <label htmlFor="month">Month:</label>
            <input
              type="number"
              value={thisMonth}
              id="month"
              name="month"
              min="1"
              max="12"
              onChange={(e) => setThisMonth(e.target.value)}
            />
          </div>
          <div>
            <label htmlFor="year">Year:</label>
            <input
              type="number"
              id="year"
              name="year"
              value={thisYear}
              min="1900"
              max="2100"
              onChange={(e) => setThisYear(e.target.value)}
            />
          </div>
          <input type="submit" value="submit" />
        </form>
      </div>
    </div>
  );
}

额外说明

  • 如果后续Calendar组件需要独立修改年月(比如左右切换月份),可以保留内部的month和year状态,同时添加useEffect监听props变化:
    useEffect(() => {
      setMonth(props.month);
      setYear(props.year);
    }, [props.month, props.year]);
    
  • 原代码中的闰年判断逻辑不完整,已修复为标准的闰年判断规则:能被4整除但不能被100整除,或者能被400整除。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 17:37:02