diff --git a/com.twin.app.shoptime/src/App/App.js b/com.twin.app.shoptime/src/App/App.js index 3e51cf8a..34b82026 100644 --- a/com.twin.app.shoptime/src/App/App.js +++ b/com.twin.app.shoptime/src/App/App.js @@ -4,15 +4,15 @@ import React, { useEffect, useRef, // useState, -} from "react"; -import { useSelector, useDispatch } from "react-redux"; +} from 'react'; +import { useSelector, useDispatch } from 'react-redux'; // import { I18nContext } from "@enact/i18n"; // import classNames from "classnames"; // import PropTypes from "prop-types"; -import Spotlight from "@enact/spotlight"; -import { Job } from "@enact/core/util"; -import platform from "@enact/core/platform"; -import { ThemeDecorator } from "@enact/sandstone/ThemeDecorator"; +import Spotlight from '@enact/spotlight'; +import { Job } from '@enact/core/util'; +import platform from '@enact/core/platform'; +import { ThemeDecorator } from '@enact/sandstone/ThemeDecorator'; // import "../../../assets/fontello/css/fontello.css"; @@ -37,20 +37,17 @@ import { setDeepLink, setGNBMenu, setSecondLayerInfo, -} from "../actions/commonActions"; -import { getShoptimeTerms } from "../actions/empActions"; -import { getHomeMenu, getHomeTerms } from "../actions/homeActions"; -import { - getMyRecommandedKeyword, - getMyUpcomingAlertShow, -} from "../actions/myPageActions"; -import { pushPanel } from "../actions/panelActions"; -import NotSupportedVersion from "../components/NotSupportedVersion/NotSupportedVersion"; -import ToastContainer from "../components/TToast/ToastContainer"; -import usePrevious from "../hooks/usePrevious"; -import { lunaTest } from "../lunaSend/lunaTest"; -import { store } from "../store/store"; -import * as Config from "../utils/Config"; +} from '../actions/commonActions'; +import { getShoptimeTerms } from '../actions/empActions'; +import { getHomeMenu, getHomeTerms } from '../actions/homeActions'; +import { getMyRecommandedKeyword, getMyUpcomingAlertShow } from '../actions/myPageActions'; +import { pushPanel } from '../actions/panelActions'; +import NotSupportedVersion from '../components/NotSupportedVersion/NotSupportedVersion'; +import ToastContainer from '../components/TToast/ToastContainer'; +import usePrevious from '../hooks/usePrevious'; +import { lunaTest } from '../lunaSend/lunaTest'; +import { store } from '../store/store'; +import * as Config from '../utils/Config'; import { // $L, clearLaunchParams, @@ -58,22 +55,20 @@ import { getLaunchParams, // getUUID, // resizeTo, -} from "../utils/helperMethods"; -import { SpotlightIds } from "../utils/SpotlightIds"; -import ErrorBoundary from "../views/ErrorBoundary"; -import MainView from "../views/MainView/MainView"; -import css from "./App.module.less"; -import { handleBypassLink } from "./bypassLinkHandler"; -import { handleDeepLink } from "./deepLinkHandler"; -import { sendLogTotalRecommend } from "../actions/logActions"; +} from '../utils/helperMethods'; +import { SpotlightIds } from '../utils/SpotlightIds'; +import ErrorBoundary from '../views/ErrorBoundary'; +import MainView from '../views/MainView/MainView'; +import css from './App.module.less'; +import { handleBypassLink } from './bypassLinkHandler'; +import { handleDeepLink } from './deepLinkHandler'; +import { sendLogTotalRecommend } from '../actions/logActions'; // import { // startFocusMonitoring, // stopFocusMonitoring, // } from "../utils/focus-monitor"; // import { PanelHoc } from "../components/TPanel/TPanel"; - - let foreGroundChangeTimer = null; // 기존 콘솔 메서드를 백업 @@ -102,7 +97,7 @@ const originalConsoleError = console.error; const originalConsoleWarn = console.warn; const processArgs = (args) => { - return args.map(arg => { + return args.map((arg) => { if (typeof arg === 'object' && arg !== null && !Array.isArray(arg)) { try { return JSON.stringify(arg, null, 2); @@ -114,15 +109,15 @@ const processArgs = (args) => { }); }; -console.log = function(...args) { +console.log = function (...args) { originalConsoleLog.apply(console, processArgs(args)); }; -console.error = function(...args) { +console.error = function (...args) { originalConsoleError.apply(console, processArgs(args)); }; -console.warn = function(...args) { +console.warn = function (...args) { originalConsoleWarn.apply(console, processArgs(args)); }; @@ -131,7 +126,7 @@ Spotlight.focus = function (elem, containerOption) { const ret = originFocus.apply(this, [elem, containerOption]); // this 바인딩을 유지하여 originFocus 호출 if (ret === true) { const current = Spotlight.getCurrent(); - const floatLayerNode = document.getElementById("floatLayer"); + const floatLayerNode = document.getElementById('floatLayer'); const tabLayoutNode = document.getElementById(SpotlightIds.TAB_LAYOUT); //팝업이 존재할 경우 if (floatLayerNode && floatLayerNode.childElementCount > 0) { @@ -147,15 +142,11 @@ Spotlight.focus = function (elem, containerOption) { } } else { //GNB가 열린 상태에서 Panel에 포커스 가는 경우 - if ( - current && - store.getState().common.isGnbOpened && - !tabLayoutNode.contains(current) - ) { + if (current && store.getState().common.isGnbOpened && !tabLayoutNode.contains(current)) { store.dispatch( sendBroadCast({ - type: "deActivateTab", - moreInfo: { reason: "focus" }, + type: 'deActivateTab', + moreInfo: { reason: 'focus' }, }) ); } @@ -168,17 +159,11 @@ function AppBase(props) { const dispatch = useDispatch(); const httpHeader = useSelector((state) => state.common.httpHeader); const httpHeaderRef = useRef(httpHeader); - const webOSVersion = useSelector( - (state) => state.common.appStatus.webOSVersion - ); + const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion); const deviceId = useSelector((state) => state.common.appStatus.deviceId); - const loginUserData = useSelector( - (state) => state.common.appStatus.loginUserData - ); + const loginUserData = useSelector((state) => state.common.appStatus.loginUserData); const loginUserDataRef = useRef(loginUserData); - const cursorVisible = useSelector( - (state) => state.common.appStatus.cursorVisible - ); + const cursorVisible = useSelector((state) => state.common.appStatus.cursorVisible); const introTermsAgree = useSelector((state) => state.common.introTermsAgree); const deviceRegistered = useSelector((state) => state.common.deviceRegistered); // const optionalTermsAgree = useSelector((state) => state.common.optionalTermsAgree); @@ -187,8 +172,6 @@ function AppBase(props) { // const termsFlag = useSelector((state) => state.common.termsFlag); const termsData = useSelector((state) => state.home.termsData); - - useEffect(() => { // Chromium68 호환성을 위해 Optional Chaining 제거 if (termsData && termsData.data && termsData.data.terms) { @@ -198,13 +181,11 @@ function AppBase(props) { const introTermsAgreeRef = usePrevious(introTermsAgree); const logEnable = useSelector((state) => state.localSettings.logEnable); - const oldDb8Deleted = useSelector( - (state) => state.localSettings.oldDb8Deleted - ); + const oldDb8Deleted = useSelector((state) => state.localSettings.oldDb8Deleted); // const macAddress = useSelector((state) => state.common.macAddress); // Chromium68 호환성을 위해 Optional Chaining 제거 - const deviceCountryCode = httpHeader && httpHeader["X-Device-Country"] || ""; + const deviceCountryCode = (httpHeader && httpHeader['X-Device-Country']) || ''; useEffect(() => { if (!cursorVisible && !Spotlight.getCurrent()) { @@ -229,7 +210,7 @@ function AppBase(props) { const hideCursor = useRef( new Job((func) => { func(); - console.log("hide cursor"); + console.log('hide cursor'); }, 5000) ); @@ -252,31 +233,29 @@ function AppBase(props) { // called by [receive httpHeader, launch, relaunch] const initService = useCallback( - (haveyInit = true) => { - // console.log( // "<<<<<<<<<<<<< appinfo >>>>>>>>>>>>{heavyInit, appinfo} ", // haveyInit, // appinfo // ); - console.log( - "[App.js] initService,httpHeaderRef.current", - httpHeaderRef.current - ); - console.log("[App.js] haveyInit", haveyInit); + console.log('[App.js] initService,httpHeaderRef.current', httpHeaderRef.current); + console.log('[App.js] haveyInit', haveyInit); if (httpHeaderRef.current) { if (haveyInit) { dispatch(changeAppStatus({ connectionFailed: false })); - if (typeof window === "object" && window.PalmSystem) { - dispatch( - changeAppStatus({ - // Chromium68 호환성을 위해 Optional Chaining 제거 - cursorVisible: window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility, - }) - ); + if (typeof window === 'object' && window.PalmSystem) { + dispatch( + changeAppStatus({ + // Chromium68 호환성을 위해 Optional Chaining 제거 + cursorVisible: + window.PalmSystem && + window.PalmSystem.cursor && + window.PalmSystem.cursor.visibility, + }) + ); } dispatch(getHomeMenu()); dispatch(getMyRecommandedKeyword()); @@ -286,7 +265,7 @@ function AppBase(props) { const launchParams = getLaunchParams(); console.log( - "initService...{haveyInit, launchParams}", + 'initService...{haveyInit, launchParams}', haveyInit, JSON.stringify(launchParams) ); @@ -303,7 +282,7 @@ function AppBase(props) { sendLogTotalRecommend({ contextName: Config.LOG_CONTEXT_NAME.ENTRY, messageId: Config.LOG_MESSAGE_ID.ENTRY_INFO, - entry_menu: "App", + entry_menu: 'App', }) ); } @@ -323,31 +302,33 @@ function AppBase(props) { ); const handleRelaunchEvent = useCallback(() => { - console.log("handleRelaunchEvent started"); + console.log('handleRelaunchEvent started'); if (introTermsAgreeRef.current) { initService(false); } - if (typeof window === "object" && window.PalmSystem) { + if (typeof window === 'object' && window.PalmSystem) { window.PalmSystem.activate(); } }, [initService, introTermsAgreeRef]); const visibilityChanged = useCallback(() => { - console.log("document is hidden", document.hidden); - console.log("document.visibilityState= ", document.visibilityState); - if (document.hidden && typeof window === "object") { + console.log('document is hidden', document.hidden); + console.log('document.visibilityState= ', document.visibilityState); + if (document.hidden && typeof window === 'object') { clearTimeout(foreGroundChangeTimer); } else { // change to foreground // set foreground flag using delay time. clearTimeout(foreGroundChangeTimer); foreGroundChangeTimer = setTimeout(() => { - console.log( - "visibility changed !!! ==> set to foreground cursorVisible", - // Chromium68 호환성을 위해 Optional Chaining 제거 - JSON.stringify(window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility) - ); // eslint-disable-line no-console - if (platform.platformName !== "webos") { + console.log( + 'visibility changed !!! ==> set to foreground cursorVisible', + // Chromium68 호환성을 위해 Optional Chaining 제거 + JSON.stringify( + window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility + ) + ); // eslint-disable-line no-console + if (platform.platformName !== 'webos') { //for debug dispatch( changeAppStatus({ @@ -355,12 +336,15 @@ function AppBase(props) { cursorVisible: !platform.touchscreen, }) ); - } else if (typeof window === "object") { + } else if (typeof window === 'object') { dispatch( changeAppStatus({ isAppForeground: true, // Chromium68 호환성을 위해 Optional Chaining 제거 - cursorVisible: window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility, + cursorVisible: + window.PalmSystem && + window.PalmSystem.cursor && + window.PalmSystem.cursor.visibility, }) ); } @@ -373,7 +357,23 @@ function AppBase(props) { dispatch(changeAppStatus({ cursorVisible: false })); Spotlight.setPointerMode(false); }; + + // throttle을 사용하여 마우스 이동 이벤트 최적화 (100ms마다 최대 1회 실행) + let lastMoveTime = 0; + const THROTTLE_MS = 100; + const mouseMoveEvent = (event) => { + const now = Date.now(); + if (now - lastMoveTime < THROTTLE_MS) { + // throttle 기간 내에는 hideCursor만 재시작 + hideCursor.current.start(() => { + dispatch(changeAppStatus({ cursorVisible: false })); + Spotlight.setPointerMode(false); + }); + return; + } + + lastMoveTime = now; dispatch(changeAppStatus({ cursorVisible: true })); Spotlight.setPointerMode(true); hideCursor.current.start(() => { @@ -381,7 +381,7 @@ function AppBase(props) { Spotlight.setPointerMode(false); }); }; - if (typeof window === "object" && window.PalmSystem) { + if (typeof window === 'object' && window.PalmSystem) { window.PalmSystem.activate(); window.lunaTest = (service, method, subscribe, parameters) => lunaTest(service, method, subscribe, parameters); @@ -392,30 +392,28 @@ function AppBase(props) { dispatch(getDeviceId()); dispatch(getHttpHeaderForServiceRequest()); dispatch(getSystemSettings()); - document.addEventListener("visibilitychange", visibilityChanged); - document.addEventListener("webOSRelaunch", handleRelaunchEvent); + document.addEventListener('visibilitychange', visibilityChanged); + document.addEventListener('webOSRelaunch', handleRelaunchEvent); //local virtual cursorvisiblilty - if (typeof window === "object" && !window.PalmSystem) { - document.addEventListener("keydown", keyDownEvent, true); - document.addEventListener("mousemove", mouseMoveEvent, true); - document.addEventListener("wheel", mouseMoveEvent, true); + if (typeof window === 'object' && !window.PalmSystem) { + document.addEventListener('keydown', keyDownEvent, true); + document.addEventListener('mousemove', mouseMoveEvent, true); + document.addEventListener('wheel', mouseMoveEvent, true); } return () => { - document.removeEventListener("visibilitychange", visibilityChanged); - document.removeEventListener("webOSRelaunch", handleRelaunchEvent); - if (typeof window === "object" && !window.PalmSystem) { - document.removeEventListener("keydown", keyDownEvent); - document.removeEventListener("mousemove", mouseMoveEvent); - document.removeEventListener("wheel", mouseMoveEvent); + document.removeEventListener('visibilitychange', visibilityChanged); + document.removeEventListener('webOSRelaunch', handleRelaunchEvent); + if (typeof window === 'object' && !window.PalmSystem) { + document.removeEventListener('keydown', keyDownEvent); + document.removeEventListener('mousemove', mouseMoveEvent); + document.removeEventListener('wheel', mouseMoveEvent); } }; }, [dispatch, visibilityChanged, handleRelaunchEvent]); useEffect(() => { let userDataChanged = false; - if ( - JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData) - ) { + if (JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData)) { userDataChanged = true; } if (!httpHeader || !deviceId) { @@ -424,7 +422,7 @@ function AppBase(props) { if (!httpHeader) { dispatch( changeAppStatus({ - showLoadingPanel: { show: true, type: "launching" }, + showLoadingPanel: { show: true, type: 'launching' }, }) ); } @@ -433,7 +431,7 @@ function AppBase(props) { dispatch( getHomeTerms({ mbrNo: loginUserData.userNumber, - trmsTpCdList: "MST00401, MST00402, MST00405", // 선택약관 추가 25.06 + trmsTpCdList: 'MST00401, MST00402, MST00405', // 선택약관 추가 25.06 }) ); @@ -447,7 +445,7 @@ function AppBase(props) { webOSVersion && Number(webOSVersion) >= 6 && deviceCountryCode && - deviceCountryCode === "US" && + deviceCountryCode === 'US' && deviceId ) { dispatch(getShoptimeTerms()); @@ -468,14 +466,12 @@ function AppBase(props) { // 약관 동의 여부 확인 전에는 아무것도 하지 않음 return; } - console.log("[App.js] deviceRegistered", deviceRegistered); + console.log('[App.js] deviceRegistered', deviceRegistered); if (introTermsAgree || deviceRegistered) { initService(true); } else { // 필수 약관에 동의하지 않은 경우 - dispatch( - pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} }) - ); + dispatch(pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); } }, [introTermsAgree, deviceRegistered, dispatch, initService, termsLoading]); @@ -483,11 +479,11 @@ function AppBase(props) { useEffect(() => { const launchParmas = getLaunchParams(); const linkTpNm = launchParmas.contentTarget - ? launchParmas.contentTarget.split("_")[2] || "" + ? launchParmas.contentTarget.split('_')[2] || '' : Config.LOG_MENU.APP; const linkTpCd = launchParmas.contentTarget - ? launchParmas.contentTarget.split("_")[1] || "" - : "1000"; + ? launchParmas.contentTarget.split('_')[1] || '' + : '1000'; if (launchParmas.contentTarget) { dispatch( @@ -501,7 +497,7 @@ function AppBase(props) { dispatch(setGNBMenu(linkTpNm)); dispatch( setSecondLayerInfo({ - deeplinkId: launchParmas.contentTarget ?? "", + deeplinkId: launchParmas.contentTarget ?? '', linkTpCd, logTpNo: Config.LOG_TP_NO.SECOND_LAYER, }) @@ -510,16 +506,13 @@ function AppBase(props) { return ( - {webOSVersion === "" ? null : Number(webOSVersion) < 4 ? ( + {webOSVersion === '' ? null : Number(webOSVersion) < 4 ? ( ) : ( )} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index d13ff682..37e862a0 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -122,19 +122,6 @@ const extractProductMeta = (productInfo) => ({ orderPhnNo: get('orderPhnNo', productInfo), }); -// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경 -const SpottableComponent = Spottable('div'); - -const LayoutSample = ({ onClick }) => ( - - Click to Show All Reviews (W-1124px) - -); - export default function ProductAllSection({ productType, productInfo, @@ -167,6 +154,9 @@ export default function ProductAllSection({ const [isShowQRCode, setIsShowQRCode] = useState(true); const timerRef = useRef(null); + // 모든 timeout/timer를 추적하기 위한 ref + const timersRef = useRef([]); + useEffect(() => { const toggleQRCode = () => { if (isShowQRCode) { @@ -415,7 +405,10 @@ export default function ProductAllSection({ const handleThemeItemButtonClick = useCallback( pipe( () => setOpenThemeItemOverlay(true), - tap(() => setTimeout(() => Spotlight.focus('theme-close-button'), 0)) + tap(() => { + const timerId = setTimeout(() => Spotlight.focus('theme-close-button'), 0); + timersRef.current.push(timerId); + }) ), [setOpenThemeItemOverlay] ); @@ -520,6 +513,24 @@ export default function ProductAllSection({ } }, [onReady, isOnRender]); + // 컴포넌트 unmount 시 모든 timer cleanup + useEffect(() => { + return () => { + // QR code timer cleanup + if (timerRef.current) { + clearTimeout(timerRef.current); + } + + // 모든 추적된 timer cleanup + timersRef.current.forEach((timerId) => { + clearTimeout(timerId); + }); + timersRef.current = []; + + console.log('[ProductAllSection] All timers cleaned up on unmount'); + }; + }, []); + return ( {/* Left Margin Section - 60px */} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx index 596e3f79..fca1e269 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx @@ -19,7 +19,7 @@ const POSITION = { /** * DetailPanel 전용 AutoScrollArea - * cursorVisible Redux 구독을 제거하여 TScrollerDetail 재렌더링 방지 + * cursorVisible을 prop으로 받아서 TScrollerDetail 재렌더링 방지 */ export default function AutoScrollAreaDetail({ position, @@ -29,9 +29,9 @@ export default function AutoScrollAreaDetail({ scrollToRef, scrollPosition, direction, + cursorVisible, }) { const requestIdRef = useRef(); - // cursorVisible Redux 구독 제거 - TScrollerDetail 재렌더링 방지 const handleFocusAutoScroll = useCallback(() => { if (!autoScroll) return; @@ -115,8 +115,8 @@ export default function AutoScrollAreaDetail({ return ( diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx index 6df0623d..25bd410f 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx @@ -32,11 +32,12 @@ const TScrollerDetail = forwardRef( autoScroll = direction === 'horizontal', setScrollVerticalPos, setCheckScrollPosition, + cursorVisible = true, // prop으로 받되 기본값은 true (대부분 cursor 표시) ...rest }, ref ) => { - // cursorVisible을 Redux에서 구독하지 않음 - children 변경 방지 + // Redux 구독 제거 - cursorVisible은 prop으로만 받음 const isScrolling = useRef(false); const scrollPosition = useRef('top'); @@ -116,29 +117,35 @@ const TScrollerDetail = forwardRef( isScrolling.current = false; - if (e.reachedEdgeInfo) { - if (e.reachedEdgeInfo.top) { - scrollPosition.current = 'top'; - } else if (e.reachedEdgeInfo.bottom) { - scrollPosition.current = 'bottom'; - } else if (e.reachedEdgeInfo.left) { - scrollPosition.current = 'left'; - } else if (e.reachedEdgeInfo.right) { - scrollPosition.current = 'right'; - } else { - scrollPosition.current = 'middle'; - } - } else { - scrollPosition.current = 'middle'; - } + // 이전 스크롤 위치 저장 + const prevScrollLeft = scrollHorizontalPos.current; + const prevScrollTop = scrollVerticalPos.current; + const prevPosition = scrollPosition.current; + // 현재 스크롤 위치 업데이트 scrollHorizontalPos.current = e.scrollLeft; scrollVerticalPos.current = e.scrollTop; - if (setScrollVerticalPos) { + // 스크롤 포지션 판단 + let newPosition = 'middle'; + if (e.reachedEdgeInfo) { + if (e.reachedEdgeInfo.top) { + newPosition = 'top'; + } else if (e.reachedEdgeInfo.bottom) { + newPosition = 'bottom'; + } else if (e.reachedEdgeInfo.left) { + newPosition = 'left'; + } else if (e.reachedEdgeInfo.right) { + newPosition = 'right'; + } + } + scrollPosition.current = newPosition; + + // 값이 실제로 변경되었을 때만 콜백 호출 (불필요한 부모 재렌더링 방지) + if (setScrollVerticalPos && prevScrollTop !== scrollVerticalPos.current) { setScrollVerticalPos(scrollVerticalPos.current); } - if (setCheckScrollPosition) { + if (setCheckScrollPosition && prevPosition !== scrollPosition.current) { setCheckScrollPosition(scrollPosition.current); } }, @@ -214,6 +221,7 @@ const TScrollerDetail = forwardRef( scrollToRef={scrollToRef} scrollPosition={scrollPosition} direction={direction} + cursorVisible={cursorVisible} /> ))} @@ -229,22 +237,5 @@ TScrollerDetail.scrollToElement = (element) => { // displayName을 명확하게 설정 TScrollerDetail.displayName = 'TScrollerDetail'; -// React.memo로 최적화 - props가 동일하면 재렌더링 방지 -const MemoizedTScrollerDetail = React.memo(TScrollerDetail, (prevProps, nextProps) => { - // children 비교는 얕은 비교로 충분 (React.Children.count 사용) - const childrenEqual = - React.Children.count(prevProps.children) === React.Children.count(nextProps.children); - - // 주요 props 비교 - return ( - childrenEqual && - prevProps.className === nextProps.className && - prevProps.verticalScrollbar === nextProps.verticalScrollbar && - prevProps.direction === nextProps.direction && - prevProps.cbScrollTo === nextProps.cbScrollTo && - prevProps.onScroll === nextProps.onScroll - ); -}); - // forwardRef를 사용하는 컴포넌트임을 명시 -export default MemoizedTScrollerDetail; +export default TScrollerDetail;