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
相关产品推荐
相关产品推荐

