讲师中心 微信公众号
AI工具推荐 视频效率加速

React Big Calendar 事件更新不重渲染的解决方案与最佳实践

老婷君_7952

老婷君_7952

发布时间:2026-02-25 08:49:06

|

807人浏览过

|

来源于php中文网

原创

React Big Calendar 事件更新不重渲染的解决方案与最佳实践

本文详解 React Big Calendar 在事件数据变更后未自动重渲染的根本原因,提供基于 useReducer 的状态管理优化方案,并附可复用的事件转换、状态同步及性能注意事项。

本文详解 react big calendar 在事件数据变更后未自动重渲染的根本原因,提供基于 `usereducer` 的状态管理优化方案,并附可复用的事件转换、状态同步及性能注意事项。

React Big Calendar 是一个功能强大但对状态更新敏感的组件——它不会自动监听嵌套对象或派生数组的深层变化。在你的代码中,events 是由 holidayEvents 和 tasks 拼接而成的计算属性(const events = [...holidayEvents, ...tasks]),而 tasks 又依赖 props.data 经 map 转换生成。问题核心在于:tasks 状态虽被 setTasks 更新,但 events 并非 React state,而是每次渲染时重新计算的普通变量;Calendar 组件仅通过 events prop 接收数据,若该 prop 引用未变(如因浅比较失效或闭包旧值),则不会触发重渲染。

更关键的是,你当前使用了两个独立的 useState(holidays 和 tasks),却未建立它们与最终 events 数组之间的响应式联动。例如:

  • useEffect(() => { setTasks(newTasks) }, []) 中的空依赖数组导致 tasks 仅在挂载时初始化,后续 props.data 变化不会触发更新;
  • addMockTask 中调用 setTasks([...tasks, taskToEvent(dummyTask)]) 确实更新了 tasks,但 holidayEvents 是在渲染函数内即时生成的(非 state),其内容依赖 holidays state —— 而 holidays 的 useEffect 是异步获取的,存在竞态风险;
  • taskToEvent 函数定义在组件内部,每次渲染都会重建,不仅影响性能,还可能干扰 memoization。

✅ 正确解法:统一状态管理 + 确保 events prop 引用稳定更新

推荐使用 useReducer 集中管理 holidays、tasks 和最终 events,确保所有状态变更原子化、可追溯,且 events 始终为最新派生值:

React 组件生成器
React 组件生成器

根据项目规范生成 React 和 Vue 组件,包括 TypeScript 类型定义、Ant Design 5 集成、Tailwind CSS 样式及 Vitest 测试文件。当用户提出创建、生成或搭建前端组件的需求时启用,例如:“帮我写一个表格组件”、“生成一个弹窗”、“create a form component”、“写一个列表页”、“生成一个下拉选择器”。该能力在以下任一场景触发:涉及 React/TSX/Vue 组件生成、UI 组件创建,或为组件生成测试文件。

下载
import { useReducer, useEffect } from 'react';
import { Calendar, momentLocalizer } from 'react-big-calendar';
import moment from 'moment';

import 'react-big-calendar/lib/css/react-big-calendar.css';
import './calendar.css';

const localizer = momentLocalizer(moment);

// ✅ 提升至模块顶层:避免重复创建
const taskToEvent = (task) => ({
  title: task.title,
  start: moment(task.start).toDate(),
  end: moment(task.due).toDate(),
  allDay: !task.due.includes('T'),
});

const holidayToEvent = (holiday) => ({
  title: holiday.nameEn,
  start: moment(holiday.date).toDate(),
  end: moment(holiday.date).toDate(),
  allDay: true,
  type: 'holiday',
  color: '#D71313',
});

// ✅ Reducer 管理完整日历状态
const calendarReducer = (state, action) => {
  switch (action.type) {
    case 'SET_HOLIDAYS': {
      const federalHolidays = action.holidays.filter(h => h.federal);
      const holidayEvents = federalHolidays.map(holidayToEvent);
      return {
        ...state,
        holidays: action.holidays,
        events: [...holidayEvents, ...state.tasks.map(taskToEvent)],
      };
    }
    case 'SET_TASKS': {
      const taskEvents = action.tasks.map(taskToEvent);
      return {
        ...state,
        tasks: action.tasks,
        events: [...state.holidays.filter(h => h.federal).map(holidayToEvent), ...taskEvents],
      };
    }
    case 'ADD_TASK': {
      const newTaskEvent = taskToEvent(action.task);
      const updatedTasks = [...state.tasks, action.task];
      const updatedEvents = [...state.events, newTaskEvent];
      return {
        ...state,
        tasks: updatedTasks,
        events: updatedEvents,
      };
    }
    default:
      return state;
  }
};

export default function TaskSnapCalendar({ data: initialTasks = [], modifyData }) {
  // ✅ 初始状态包含 tasks & events(预计算)
  const initialState = {
    holidays: [],
    tasks: initialTasks,
    events: initialTasks.map(taskToEvent),
  };

  const [state, dispatch] = useReducer(calendarReducer, initialState);

  // ✅ 加载节假日并同步到 events
  useEffect(() => {
    fetch('https://canada-holidays.ca/api/v1/holidays')
      .then(res => res.json())
      .then(data => dispatch({ type: 'SET_HOLIDAYS', holidays: data.holidays }));
  }, []);

  // ✅ 响应父组件 props.data 变更(关键!)
  useEffect(() => {
    dispatch({ type: 'SET_TASKS', tasks: initialTasks });
  }, [initialTasks]);

  // ✅ 模拟添加任务(真实场景中由 modifyData 触发)
  const addMockTask = () => {
    const dummyTask = {
      id: '200',
      title: 'DUMMY TASK',
      status: 'ongoing',
      assigned_to: 'DUMMY',
      start: '2023-08-24',
      due: '2023-08-25', // ⚠️ 注意:原示例中 due 为 2022 年,逻辑上应晚于 start
      label: 'DUMMY',
      description: '',
    };
    modifyData(prev => [...prev, dummyTask]);
    dispatch({ type: 'ADD_TASK', task: dummyTask });
  };

  return (
    <div className="content">
      <Calendar
        className="main-calendar"
        localizer={localizer}
        events={state.events} // ✅ 始终是最新引用的数组
        startAccessor="start"
        endAccessor={(event) =>
          event.allDay
            ? moment(event.end).add(1, 'days').toDate()
            : event.end
        }
        showMultiDayTimes={true}
        eventPropGetter={(event) => ({
          style: { backgroundColor: event.color || '#007bff' },
        })}
      />
      <button onClick={addMockTask}>TEST — Add Dummy Task</button>
    </div>
  );
}

? 关键改进说明:

  • 状态原子性:events 不再是临时变量,而是 reducer state 的一部分,任何任务/假日变更都必然更新 state.events,Calendar 收到新数组引用即重渲染;
  • 响应式 Props 同步:新增 useEffect 监听 initialTasks,确保父组件传入新任务列表时自动刷新本地 tasks 和 events;
  • 性能优化:taskToEvent / holidayToEvent 移至组件外,避免闭包重建;endAccessor 返回 Date 对象(而非字符串),符合 RBC 类型要求;
  • 类型安全提示:注意 due 日期不应早于 start,否则可能导致日历显示异常(如跨年事件错位);

? 额外建议:

  • 若 modifyData 是用于全局状态(如 Redux 或 Context),建议在 modifyData 成功后 dispatch 对应 action,而非仅靠 props.data 变更驱动;
  • 对大量事件(>500 条),启用 views={{ month: true, week: true, day: true }} 并结合 onRangeChange 懒加载,避免初始渲染卡顿;
  • 使用 React.memo 包裹自定义 EventComponent,进一步提升滚动性能。

遵循以上结构,即可彻底解决 “事件更新但日历不重绘” 的常见陷阱,构建稳定、可维护的日程类应用。

热门AI工具

更多
创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

讯飞智文

一款面向学习和办公场景的AI文档创作工具,可辅助生成PPT与Word文档,提高资料整理和内容制作效率。

相关专题

更多
java中calendar类的用法
java中calendar类的用法

Java Video类是JavaFX库中的一个类,用于创建和操作视频对象。它提供了方法来加载、播放、暂停、停止和控制视频的音量、速度和循环等属性。想了解更多Java中类的相关内容,可以阅读本专题下面的文章。

1975

2024.02.29

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2418

2023.09.20

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1898

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

3084

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6324

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

6369

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

7934

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

12154

2024.04.29

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

0

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 12.5万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn