[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:
2025-10-11 21:35:01 +09:00
parent 110cd2760a
commit 122020a8f7
4 changed files with 168 additions and 173 deletions

View File

@@ -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 (
<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
}
/>
)}