0

0

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

碧海醫心

碧海醫心

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

|

787人浏览过

|

来源于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 始终为最新派生值:

HIX.AI
HIX.AI

HIX.AI是一个多功能的一体化AI写作助手,集成了120多种AI写作工具,支持50多种语言,能够满足各种写作需求。

下载
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工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

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

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

324

2024.02.29

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

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

552

2023.09.20

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

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

638

2023.08.03

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

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

217

2023.09.04

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

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

1558

2023.10.24

字符串介绍
字符串介绍

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

642

2023.11.24

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

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

1027

2024.03.22

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

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

980

2024.04.29

Golang 生态工具与框架:扩展开发能力
Golang 生态工具与框架:扩展开发能力

《Golang 生态工具与框架》系统梳理 Go 语言在实际工程中的主流工具链与框架选型思路,涵盖 Web 框架、RPC 通信、依赖管理、测试工具、代码生成与项目结构设计等内容。通过真实项目场景解析不同工具的适用边界与组合方式,帮助开发者构建高效、可维护的 Go 工程体系,并提升团队协作与交付效率。

1

2026.02.24

热门下载

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

精品课程

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

共58课时 | 5.4万人学习

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

共12课时 | 1万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1.1万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号