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

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;
}

问题根源

  1. handleDateSelect的依赖数组包含selectedDates.start和selectedDates.end,每次选中状态变化时,回调函数会重新创建,导致所有CalendarDay的handleDateSelect prop更新,触发重渲染。
  2. 虽然子组件用了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 14:10:58