From 1b330872f514721a3db4b09f397f0b8c991d96a2 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 7 Jun 2026 16:19:33 +0900 Subject: [PATCH] =?UTF-8?q?perf(schedule):=20=EC=B9=B4=EB=93=9C=20onClick?= =?UTF-8?q?=20=EC=95=88=EC=A0=95=ED=99=94=EB=A1=9C=20React.memo=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 카드가 onClick(schedule)을 호출하도록 변경(기존 호출부는 인자 무시라 호환), 페이지는 useCallback 안정 핸들러를 전달. 매 렌더 새 인라인 함수로 memo가 깨져 필터/스크롤마다 전체 카드가 리렌더되던 문제 해결. 검색 결과 가상화 카드는 범위에서 제외(이미 최적화됨). Co-Authored-By: Claude Opus 4.7 --- .../components/mobile/schedule/BirthdayCard.jsx | 4 ++-- .../mobile/schedule/ScheduleListCard.jsx | 2 +- .../mobile/schedule/UndatedScheduleListCard.jsx | 2 +- .../components/pc/public/schedule/BirthdayCard.jsx | 2 +- .../components/pc/public/schedule/ScheduleCard.jsx | 2 +- .../pc/public/schedule/UndatedScheduleCard.jsx | 2 +- frontend/src/pages/mobile/schedule/Schedule.jsx | 13 +++++++++---- frontend/src/pages/pc/public/schedule/Schedule.jsx | 14 +++++++------- 8 files changed, 23 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/mobile/schedule/BirthdayCard.jsx b/frontend/src/components/mobile/schedule/BirthdayCard.jsx index efc603f..6a6730f 100644 --- a/frontend/src/components/mobile/schedule/BirthdayCard.jsx +++ b/frontend/src/components/mobile/schedule/BirthdayCard.jsx @@ -67,7 +67,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, de initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }} - onClick={onClick} + onClick={() => onClick?.(schedule)} className="cursor-pointer" > {CardContent} @@ -76,7 +76,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, de } return ( -
+
onClick?.(schedule)} className="cursor-pointer"> {CardContent}
); diff --git a/frontend/src/components/mobile/schedule/ScheduleListCard.jsx b/frontend/src/components/mobile/schedule/ScheduleListCard.jsx index 681e28f..723b30e 100644 --- a/frontend/src/components/mobile/schedule/ScheduleListCard.jsx +++ b/frontend/src/components/mobile/schedule/ScheduleListCard.jsx @@ -24,7 +24,7 @@ const ScheduleListCard = memo(function ScheduleListCard({ initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }} - onClick={onClick} + onClick={() => onClick?.(schedule)} className={`cursor-pointer ${className}`} > {/* 카드 본체 (플랫 테두리) */} diff --git a/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx b/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx index 0e155ee..6ff12be 100644 --- a/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx +++ b/frontend/src/components/mobile/schedule/UndatedScheduleListCard.jsx @@ -26,7 +26,7 @@ const UndatedScheduleListCard = memo(function UndatedScheduleListCard({ initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }} - onClick={onClick} + onClick={() => onClick?.(schedule)} className={`cursor-pointer ${className}`} > {/* 카드 본체 (점선 테두리) */} diff --git a/frontend/src/components/pc/public/schedule/BirthdayCard.jsx b/frontend/src/components/pc/public/schedule/BirthdayCard.jsx index 8fa3d30..189ace5 100644 --- a/frontend/src/components/pc/public/schedule/BirthdayCard.jsx +++ b/frontend/src/components/pc/public/schedule/BirthdayCard.jsx @@ -14,7 +14,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, on return (
onClick?.(schedule)} className="relative overflow-hidden bg-gradient-to-r from-pink-400 via-purple-400 to-indigo-400 rounded-2xl shadow-lg hover:shadow-xl transition-shadow cursor-pointer" > {/* 배경 장식 */} diff --git a/frontend/src/components/pc/public/schedule/ScheduleCard.jsx b/frontend/src/components/pc/public/schedule/ScheduleCard.jsx index e03d79a..e7e2697 100644 --- a/frontend/src/components/pc/public/schedule/ScheduleCard.jsx +++ b/frontend/src/components/pc/public/schedule/ScheduleCard.jsx @@ -26,7 +26,7 @@ const ScheduleCard = memo(function ScheduleCard({ schedule, onClick, className = return (
onClick?.(schedule)} className={`flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden cursor-pointer ${className}`} > {/* 날짜 영역 */} diff --git a/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx b/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx index 4fb833a..186a052 100644 --- a/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx +++ b/frontend/src/components/pc/public/schedule/UndatedScheduleCard.jsx @@ -17,7 +17,7 @@ const UndatedScheduleCard = memo(function UndatedScheduleCard({ schedule, onClic return (
onClick?.(schedule)} className={`flex items-stretch bg-white rounded-2xl border-2 border-dashed border-gray-300 hover:border-gray-400 transition-colors overflow-hidden cursor-pointer ${className}`} > {/* 월 영역 (연한 카테고리 색) */} diff --git a/frontend/src/pages/mobile/schedule/Schedule.jsx b/frontend/src/pages/mobile/schedule/Schedule.jsx index de5d203..90e78af 100644 --- a/frontend/src/pages/mobile/schedule/Schedule.jsx +++ b/frontend/src/pages/mobile/schedule/Schedule.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo, useRef } from 'react'; +import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react'; @@ -44,6 +44,11 @@ function MobileSchedule() { ); const setSelectedDate = (date) => setStoredSelectedDate(date); + // 카드 클릭 핸들러 (안정적 참조로 카드 React.memo 유지) + const handleCardClick = useCallback((schedule) => { + navigate(`/schedule/${schedule.id}`); + }, [navigate]); + const [isSearchMode, setIsSearchMode] = useState(false); const [searchInput, setSearchInput] = useState(''); const [searchTerm, setSearchTerm] = useState(''); @@ -888,7 +893,7 @@ function MobileSchedule() { key={schedule.id} schedule={schedule} delay={index * 0.05} - onClick={() => navigate(`/schedule/${schedule.id}`)} + onClick={handleCardClick} /> ); } @@ -908,7 +913,7 @@ function MobileSchedule() { key={schedule.id} schedule={schedule} delay={index * 0.05} - onClick={() => navigate(`/schedule/${schedule.id}`)} + onClick={handleCardClick} /> ); })} @@ -926,7 +931,7 @@ function MobileSchedule() { key={`undated-${schedule.id}`} schedule={schedule} delay={(selectedDateSchedules.length + index) * 0.05} - onClick={() => navigate(`/schedule/${schedule.id}`)} + onClick={handleCardClick} /> ))}
diff --git a/frontend/src/pages/pc/public/schedule/Schedule.jsx b/frontend/src/pages/pc/public/schedule/Schedule.jsx index ae96c69..39af418 100644 --- a/frontend/src/pages/pc/public/schedule/Schedule.jsx +++ b/frontend/src/pages/pc/public/schedule/Schedule.jsx @@ -292,7 +292,7 @@ function PCSchedule() { }); // 일정 클릭 핸들러 - const handleScheduleClick = (schedule) => { + const handleScheduleClick = useCallback((schedule) => { // 생일, 데뷔, 주년 등 특수 일정 if (schedule.is_birthday || schedule.is_debut || schedule.is_anniversary) { navigate(`/schedule/${schedule.id}`); @@ -309,7 +309,7 @@ function PCSchedule() { } else { navigate(`/schedule/${schedule.id}`); } - }; + }, [navigate]); // 카테고리 토글 const toggleCategory = (categoryId) => { @@ -586,11 +586,11 @@ function PCSchedule() { >
{schedule.is_birthday ? ( - handleScheduleClick(schedule)} /> + ) : schedule.is_debut || schedule.is_anniversary ? ( ) : ( - handleScheduleClick(schedule)} /> + )}
@@ -618,11 +618,11 @@ function PCSchedule() { transition={{ delay: Math.min(index, 10) * 0.03 }} > {schedule.is_birthday ? ( - handleScheduleClick(schedule)} /> + ) : schedule.is_debut || schedule.is_anniversary ? ( ) : ( - handleScheduleClick(schedule)} /> + )} ))} @@ -642,7 +642,7 @@ function PCSchedule() { animate={{ opacity: 1 }} transition={{ delay: Math.min(filteredSchedules.length + index, 10) * 0.03 }} > - handleScheduleClick(schedule)} /> + ))}