React日历组件切换年份时Hooks状态重置为默认值问题
问题:修改年月后日历组件状态重置为默认值
开发日历组件时,在MainShell中修改年月并提交后,Calendar组件内的年月状态会重置为默认值,使用e.preventDefault()也无法解决。
问题根源
- 子组件未同步父组件props更新:Calendar组件内部用
useState(props.month)和useState(props.year)初始化状态,但useState的初始值仅在组件挂载时生效,后续父组件传入的props变化不会自动更新子组件的state。 - useEffect依赖项缺失:生成日历的useEffect依赖数组为空,仅在组件首次挂载时执行一次,后续年月变更不会触发日历重新计算。
- 代码存在逻辑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
相关产品推荐
相关产品推荐

