基于React Hooks实现日历与预约渲染的优化方案咨询
Hey there! Let's fix that inefficient appointment rendering issue you're dealing with. Traversing your entire appointments array for every calendar cell feels clunky, and we can definitely do better while sticking to React's best practices.
The Core Problem
Right now, your calendarApts component loops through every time slot for every day, and if you were checking for appointments in each cell, that would mean an O(n*m) complexity (n = days, m = time slots) plus repeated iterations over your appointments array. That's unnecessary—we can pre-process the appointments to make lookups instant.
The Solution: Pre-Process Appointments into a Lookup Object
Instead of looping through all appointments for each cell, we'll convert the appointments array into a key-value object where the key is the timestamp of the appointment (matching the timestamp your cells generate). This way, when rendering a cell, we can just look up the timestamp in the object directly (O(1) time) instead of looping through all appointments.
Here's how to implement this:
1. Create a Memoized Appointment Lookup
Add this useMemo hook to pre-process your appointments. It will only re-run when calendar.appointments changes, so no extra computations on every render:
const appointmentsByTimestamp = useMemo(() => { if (!calendar.appointments) return {}; // Convert appointments array to an object keyed by timestamp return calendar.appointments.reduce((acc, apt) => { // Make sure this timestamp matches the format generated by convertStringToTimestamp // If your appointment has a date string instead of a unix timestamp, convert it here: // const aptTimestamp = moment(apt.dateTime, 'DD/MM/YY HH:mm').unix(); const aptTimestamp = apt.timestamp; // Replace with your actual timestamp field acc[aptTimestamp] = apt; return acc; }, {}); }, [calendar.appointments]);
2. Render Appointments Directly in Cells
Modify your calendarApts cell rendering to use the lookup object. Now you can check if an appointment exists for the cell's timestamp in one step:
const calendarApts = useMemo(() => { if (!settings.currentWeek) return; return settings.currentWeek.map(day => ( <div className="line" key={day}> {timeline.map(time => { const cellTimestamp = convertStringToTimestamp(day, time); const appointment = appointmentsByTimestamp[cellTimestamp]; return ( <div key={`${day} ${time}`} time={cellTimestamp} onClick={() => console.log(day, time)} className={appointment ? "has-appointment" : ""} > {/* Render your appointment content here if it exists */} {appointment && ( <div className="appointment-card"> <span>{appointment.title}</span> <span>{appointment.clientName}</span> </div> )} </div> ); })} </div> )); }, [settings.currentWeek, timeline, appointmentsByTimestamp]);
Bonus: Fixing Small Issues in Your Code
While we're at it, let's clean up a couple of things that could cause bugs or unnecessary re-renders:
Remove setState from useMemo: Your
timelinehook usessetSettingsinsideuseMemo, which is a side effect and shouldn't be there.timelineis already a memoized array—you can use it directly instead of storing it insettings:const timeline = useMemo(() => { let currTime = '10:00'; const stopTime = '19:00'; const interval = 15; let timesArr = []; while (currTime !== stopTime) { timesArr.push(currTime); currTime = moment(currTime, 'HH:mm').add(interval, 'minute').format('HH:mm'); } return timesArr; }, []);Fix Stale Closures in useEffect: Your
useEffecthooks use the oldsettingsstate when updating. Use functional updates to get the latest state:useEffect(() => { setCalendar(calendarFromStore); setSettings(prev => ({ ...prev, currentWeek: getCurrentWeek })); }, [calendarFromStore, getCurrentWeek]); useEffect(() => { setSettings(prev => ({ ...prev, currentWeek: getCurrentWeek })); }, [settings.diffWeeks, getCurrentWeek]);
Why This Works
- Performance: Pre-processing the appointments is O(k) (k = number of appointments), and each cell lookup is O(1). This cuts down on redundant loops drastically, especially as your appointment list grows.
- React-Friendly: We're still using state and memoization to drive rendering—no direct DOM manipulation, which aligns with React's principles.
- Maintainability: The code is cleaner, with clear separation between data processing and rendering.
内容的提问来源于stack exchange,提问作者danivegas

