frontend 속도 개선

This commit is contained in:
Macbook
2026-06-11 00:14:14 +09:00
parent 0ff1992b64
commit 54fdc3e77a
15 changed files with 522 additions and 71 deletions
+63 -10
View File
@@ -2,6 +2,9 @@ import React, { createContext, useContext } from 'react';
import type { ChartMode, IndicatorConfig } from '../types';
import type { TickerData } from '../hooks/useMarketTicker';
import type { SettingsPageChartBindings } from './useChartWorkspace';
import { DEFAULT_DISPLAY_TIMEZONE } from '../utils/timezone';
import { DEFAULT_CHART_TIME_FORMAT } from '../utils/chartTimeFormat';
import { DEFAULT_TRADE_ALERT_TIME_FORMAT } from '../utils/tradeAlertTimeFormat';
export interface ChartWorkspaceSharedState {
symbol: string;
@@ -21,12 +24,66 @@ export interface ChartWorkspaceSharedState {
settingsPageProps: SettingsPageChartBindings;
}
export interface ChartWorkspaceContextValue extends ChartWorkspaceSharedState {
/** 실시간 차트 화면 — `.app` flex 레이아웃 안에서 렌더해야 좌·우 패널·메뉴가 정상 표시됨 */
chartScreen: React.ReactNode;
}
export type ChartWorkspaceContextValue = ChartWorkspaceSharedState;
const ChartWorkspaceContext = createContext<ChartWorkspaceContextValue | null>(null);
const NULL_SETTINGS_PAGE_PROPS: SettingsPageChartBindings = {
magnetMode: 'off',
onMagnetMode: () => {},
liveStrategies: [],
onClearLiveMarkers: () => {},
liveSettings: { market: '', strategyId: null, isLiveCheck: false, executionType: 'CANDLE_CLOSE', positionMode: 'LONG_ONLY' },
onLiveSettingsChange: () => {},
watchlistCount: 0,
monitoredMarkets: [],
virtualDriven: false,
chartIndicators: [],
onAddIndicatorToChart: () => {},
onRemoveIndicatorFromChart: () => {},
onIndicatorListOrderChange: () => {},
chartCandleAreaPriceLabels: false,
onChartCandleAreaPriceLabels: () => {},
chartSeriesPriceLabels: false,
onChartSeriesPriceLabels: () => {},
chartVolumeVisible: true,
onChartVolumeVisible: () => {},
chartLiveReceiveHighlight: true,
onChartLiveReceiveHighlight: () => {},
chartLegendOptions: undefined,
onChartLegendOptionsChange: () => {},
chartCrosshairInfoVisible: true,
onChartCrosshairInfoVisible: () => {},
chartHoverToolbarVisible: true,
onChartHoverToolbarVisible: () => {},
chartPaneSeparator: undefined,
onChartPaneSeparatorChange: () => {},
onPaperAccountReset: async () => {},
onDisplayTimezoneChange: () => {},
onChartTimeFormatChange: () => {},
onTradeAlertTimeFormatChange: () => {},
displayTimezone: DEFAULT_DISPLAY_TIMEZONE,
chartTimeFormat: DEFAULT_CHART_TIME_FORMAT,
tradeAlertTimeFormat: DEFAULT_TRADE_ALERT_TIME_FORMAT,
};
export const NULL_CHART_WORKSPACE_CONTEXT: ChartWorkspaceContextValue = {
symbol: 'KRW-BTC',
indicators: [],
goToMarketChart: () => {},
refreshPaperAccount: async () => {},
paperCashBalance: 0,
paperCoinQty: 0,
handlePaperOrderFilled: () => {},
paperRefreshKey: 0,
mode: 'chart',
priorityMarkets: [],
marketPanelOpen: false,
marketTickers: new Map(),
marketLoading: false,
usdRate: 0,
settingsPageProps: NULL_SETTINGS_PAGE_PROPS,
};
const ChartWorkspaceContext = createContext<ChartWorkspaceContextValue>(NULL_CHART_WORKSPACE_CONTEXT);
export function ChartWorkspaceProvider({
value,
@@ -43,9 +100,5 @@ export function ChartWorkspaceProvider({
}
export function useChartWorkspaceContext(): ChartWorkspaceContextValue {
const ctx = useContext(ChartWorkspaceContext);
if (!ctx) {
throw new Error('useChartWorkspaceContext must be used within ChartWorkspaceProvider');
}
return ctx;
return useContext(ChartWorkspaceContext);
}
+24 -10
View File
@@ -1,4 +1,4 @@
import React, { forwardRef, useImperativeHandle, useMemo } from 'react';
import React, { forwardRef, useImperativeHandle, useMemo, useEffect, useRef } from 'react';
import { LiveSignalNotifier } from '../components/LiveSignalNotifier';
import { ChartWorkspaceProvider } from './ChartWorkspaceContext';
import { ChartWorkspaceView } from './ChartWorkspaceView';
@@ -7,12 +7,14 @@ import type { ChartWorkspaceBridge } from './chartWorkspaceBridge';
export interface ChartWorkspaceRootProps extends UseChartWorkspaceParams {
visible: boolean;
children: React.ReactNode;
/** 외부에서 이동 요청된 마켓 (알림·대시보드 등에서 차트로 이동 시) */
pendingChartMarket?: string | null;
onPendingChartMarketConsumed?: () => void;
}
export const ChartWorkspaceRoot = forwardRef<ChartWorkspaceBridge, ChartWorkspaceRootProps>(
function ChartWorkspaceRoot(props, ref) {
const { visible, children, ...workspaceParams } = props;
const { visible, pendingChartMarket, onPendingChartMarketConsumed, ...workspaceParams } = props;
const {
bridge,
contextValue,
@@ -23,19 +25,31 @@ export const ChartWorkspaceRoot = forwardRef<ChartWorkspaceBridge, ChartWorkspac
useImperativeHandle(ref, () => bridge, [bridge]);
// 외부에서 마켓 이동 요청 처리
const pendingRef = useRef(pendingChartMarket);
pendingRef.current = pendingChartMarket;
const consumedRef = useRef(onPendingChartMarketConsumed);
consumedRef.current = onPendingChartMarketConsumed;
useEffect(() => {
if (pendingChartMarket) {
bridge.goToMarket(pendingChartMarket);
consumedRef.current?.();
}
// bridge.goToMarket is stable (useCallback), pendingChartMarket is the trigger
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pendingChartMarket]);
const providerValue = useMemo(() => ({
...contextValue,
chartScreen: (
<div style={{ display: visible ? 'contents' : 'none' }}>
<ChartWorkspaceView {...viewProps} />
</div>
),
}), [contextValue, visible, viewProps]);
}), [contextValue]);
return (
<ChartWorkspaceProvider value={providerValue}>
<LiveSignalNotifier ref={liveNotifierRef} {...liveNotifierProps} />
{children}
<div style={{ display: visible ? 'contents' : 'none' }}>
<ChartWorkspaceView {...viewProps} />
</div>
</ChartWorkspaceProvider>
);
},
@@ -1,3 +1,4 @@
import '../styles/tradeRightPanel.css';
import React from 'react';
import DrawingToolbar from '../components/DrawingToolbar';
import BottomBar from '../components/BottomBar';
+3 -1
View File
@@ -1175,6 +1175,8 @@ export function useChartWorkspace({
// ── Watchlist 가격 맵 ──────────────────────────────────────────────────
const priceMap = useMemo(() => {
// 시세 패널이 열려 있고 ticker가 활성 상태일 때만 계산 (데모 심볼용)
if (!showMarketPanel || !tickerFeedActive) return {} as Record<string, { price: number; changePct: number }>;
const map: Record<string, { price: number; changePct: number }> = {};
for (const sym of watchlist) {
const b = isUpbitMarket(sym) ? [] : generateBars(sym, '1D');
@@ -1183,7 +1185,7 @@ export function useChartWorkspace({
}
}
return map;
}, [watchlist]);
}, [watchlist, showMarketPanel, tickerFeedActive]);
// ── DB 워크스페이스 동기화 ─────────────────────────────────────────────