[251011] fix: ProudctAllSection scroller 리렌더링 최적화-2
🕐 커밋 시간: 2025. 10. 11. 21:34:59 📊 변경 통계: • 총 파일: 4개 • 추가: +68줄 • 삭제: -50줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/App/App.js ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/App/App.js (javascript): 🔄 Modified: function() 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): ❌ Deleted: curry(), LayoutSample() 📄 com.twin.app.shoptime/src/views/DetailPanel/components/AutoScrollAreaDetail/AutoScrollAreaDetail.jsx (javascript): ❌ Deleted: Spottable() 📄 com.twin.app.shoptime/src/views/DetailPanel/components/TScroller/TScrollerDetail.jsx (javascript): ❌ Deleted: scrollToElement() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -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,29 +233,27 @@ 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) {
|
||||
if (typeof window === 'object' && window.PalmSystem) {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
// Chromium68 호환성을 위해 Optional Chaining 제거
|
||||
cursorVisible: window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility,
|
||||
cursorVisible:
|
||||
window.PalmSystem &&
|
||||
window.PalmSystem.cursor &&
|
||||
window.PalmSystem.cursor.visibility,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -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,19 +302,19 @@ 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
|
||||
@@ -343,11 +322,13 @@ function AppBase(props) {
|
||||
clearTimeout(foreGroundChangeTimer);
|
||||
foreGroundChangeTimer = setTimeout(() => {
|
||||
console.log(
|
||||
"visibility changed !!! ==> set to foreground cursorVisible",
|
||||
'visibility changed !!! ==> set to foreground cursorVisible',
|
||||
// Chromium68 호환성을 위해 Optional Chaining 제거
|
||||
JSON.stringify(window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility)
|
||||
JSON.stringify(
|
||||
window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility
|
||||
)
|
||||
); // eslint-disable-line no-console
|
||||
if (platform.platformName !== "webos") {
|
||||
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 (
|
||||
<ErrorBoundary>
|
||||
{webOSVersion === "" ? null : Number(webOSVersion) < 4 ? (
|
||||
{webOSVersion === '' ? null : Number(webOSVersion) < 4 ? (
|
||||
<NotSupportedVersion />
|
||||
) : (
|
||||
<MainView
|
||||
initService={initService}
|
||||
className={
|
||||
typeof window === "object" &&
|
||||
!window.PalmSystem &&
|
||||
!cursorVisible &&
|
||||
css.preventMouse
|
||||
typeof window === 'object' && !window.PalmSystem && !cursorVisible && css.preventMouse
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -122,19 +122,6 @@ const extractProductMeta = (productInfo) => ({
|
||||
orderPhnNo: get('orderPhnNo', productInfo),
|
||||
});
|
||||
|
||||
// 레이아웃 확인용 샘플 컴포넌트 - Spottable로 변경
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
const LayoutSample = ({ onClick }) => (
|
||||
<SpottableComponent
|
||||
className={css.layoutSample}
|
||||
onClick={onClick}
|
||||
spotlightId="layout-sample-button"
|
||||
>
|
||||
Click to Show All Reviews (W-1124px)
|
||||
</SpottableComponent>
|
||||
);
|
||||
|
||||
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 (
|
||||
<HorizontalContainer className={css.detailArea}>
|
||||
{/* Left Margin Section - 60px */}
|
||||
|
||||
@@ -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 (
|
||||
<AutoScrollComponent
|
||||
className={classNames(css.autoScrollArea, position && css[position])}
|
||||
spotlightDisabled={false}
|
||||
disabled={false}
|
||||
spotlightDisabled={!cursorVisible}
|
||||
disabled={!cursorVisible}
|
||||
onFocus={handleFocusAutoScroll}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user