React日期范围选择器:如何避免CalendarDay子组件不必要重渲染?
React日期范围选择器子组件全局重渲染优化
我正在用React.js开发日期范围选择器,目前遇到问题:点击日历中的某一天时,所有CalendarDay子组件都会触发重渲染,希望实现仅被点击的日期或选中范围内的日期才触发重渲染。
现有代码
主日历组件代码
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import CalendarDay from './CalendarDay'; export default function Calendar(props) { const days = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ]; const [selectedDates, setSelectedDates] = useState({ start: null, end: null, }); useEffect(() => { console.log(selectedDates); }, [selectedDates]); const handleDateSelect = useCallback( (day) => { if (!selectedDates.start) { setSelectedDates((prev) => ({ ...prev, start: day })); } else if (!selectedDates.end) { setSelectedDates((prev) => ({ ...prev, end: day })); } else { setSelectedDates({ start: day, end: null }); } }, [selectedDates.start, selectedDates.end] ); return ( <> <button onClick={() => setSelectedDates({ start: null, end: null })}> clear dates </button> <div>start: {selectedDates.start}</div> <div>end: {selectedDates.end}</div> <div className="calendar"> {days.map((day, idx) => { const key = `${props.year}-${props.month}-${idx + 1}`; const isSelectedOrInRange = () => { if ( selectedDates.start === day || selectedDates.end === day || (day <= selectedDates.end && day >= selectedDates.start) ) { return true; } return false; }; return ( <CalendarDay key={key} day={day} isSelectedOrInRange={isSelectedOrInRange()} handleDateSelect={handleDateSelect} /> ); })} </div> </> ); }
CalendarDay子组件代码
import React, { memo } from 'react'; function CalendarDay(props) { const { handleDateSelect, day, isSelectedOrInRange } = props; console.log(' - rendering: ', day); return ( <div className={'day' + (isSelectedOrInRange ? ' selected' : '')} onClick={() => handleDateSelect(day)} > {day} </div> ); } export default memo(CalendarDay);
样式文件styles.css
* { box-sizing: border-box; } .calendar { width: 400px; display: flex; flex-wrap: wrap; } .day { width: calc(400px / 7); height: 50px; border: 1px solid green; text-align: center; display: flex; justify-content: center; align-items: center; } .selected { color: white; background: red; }
问题根源
handleDateSelect的依赖数组包含selectedDates.start和selectedDates.end,每次选中状态变化时,回调函数会重新创建,导致所有CalendarDay的handleDateSelectprop更新,触发重渲染。- 虽然子组件用了
memo做浅比较,但由于父组件传递的handleDateSelect引用每次都变,memo的优化效果失效。
优化方案
1. 稳定handleDateSelect的引用
修改handleDateSelect,使用函数式更新完全依赖prevState,移除对selectedDates的依赖,保证函数引用始终不变:
const handleDateSelect = useCallback( (day) => { setSelectedDates((prev) => { if (!prev.start) { return { ...prev, start: day }; } else if (!prev.end) { return { ...prev, end: day }; } else { return { start: day, end: null }; } }); }, [] // 空依赖数组,函数引用保持稳定 );
2. 优化选中状态计算逻辑
移除render中的函数包裹,直接计算isSelectedOrInRange的值,同时增加空值判断避免逻辑错误:
{days.map((day, idx) => { const key = `${props.year}-${props.month}-${idx + 1}`; const isSelectedOrInRange = selectedDates.start === day || selectedDates.end === day || (selectedDates.start && selectedDates.end && day >= selectedDates.start && day <= selectedDates.end); return ( <CalendarDay key={key} day={day} isSelectedOrInRange={isSelectedOrInRange} handleDateSelect={handleDateSelect} /> ); })}
优化效果
完成上述修改后,只有当日期的选中状态(isSelectedOrInRange)发生变化时,对应的CalendarDay组件才会触发重渲染,其余未改变状态的日期组件不会重新渲染,大幅减少不必要的渲染开销。
内容的提问来源于stack exchange,提问作者ProEvilz
相关产品推荐
相关产品推荐

