| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- 'use client';
- import { useEffect, useRef, useState, useCallback } from 'react';
- import { useSignalRContext } from '@/contexts/signalrProvider';
- import { fetchApi } from '@/lib/utils/client';
- import type { TickerData, TickerRestData, TickerMeta } from '@/types/crypto';
- function restToTickerData(t: TickerRestData): TickerData {
- return {
- market: t.market,
- symbol: t.symbol,
- openingPrice: t.openingPrice,
- highPrice: t.highPrice,
- lowPrice: t.lowPrice,
- tradePrice: t.tradePrice,
- prevClosingPrice: t.prevClosingPrice,
- change: t.change,
- changePrice: t.changePrice,
- signedChangePrice: t.signedChangePrice,
- changeRate: t.changeRate,
- signedChangeRate: t.signedChangeRate,
- tradeVolume: t.tradeVolume,
- accTradeVolume: t.accTradeVolume,
- accTradeVolume24h: t.accTradeVolume24h,
- accTradePrice: t.accTradePrice,
- accTradePrice24h: t.accTradePrice24h,
- tradeDate: '',
- tradeTime: '',
- tradeTimestamp: 0,
- askBid: '',
- accAskVolume: 0,
- accBidVolume: 0,
- highest52WeekPrice: 0,
- highest52WeekDate: '',
- lowest52WeekPrice: 0,
- lowest52WeekDate: '',
- marketState: 'ACTIVE',
- delistingDate: null,
- marketWarning: '',
- timestamp: 0,
- streamType: '',
- };
- }
- function extractMeta(t: TickerRestData): TickerMeta {
- return {
- korName: t.korName,
- engName: t.engName,
- logoImage: t.logoImage,
- };
- }
- export default function useTickers(quoteMarket: string = 'KRW', initialTickers?: TickerRestData[]) {
- const { cryptoConnection, cryptoConnected } = useSignalRContext();
- const [tickers, setTickers] = useState<Map<string, TickerData>>(new Map());
- const [meta, setMeta] = useState<Map<string, TickerMeta>>(new Map());
- const tickersRef = useRef<Map<string, TickerData>>(new Map());
- const metaRef = useRef<Map<string, TickerMeta>>(new Map());
- const updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
- const subscribedQuoteRef = useRef<string | null>(null);
- const initializedRef = useRef(false);
- // 초기 REST 데이터 로드
- useEffect(() => {
- // initialTickers는 KRW일 때만 사용 (서버에서 KRW로 패칭)
- if (!initializedRef.current && initialTickers && initialTickers.length > 0 && quoteMarket === 'KRW') {
- const tickerMap = new Map<string, TickerData>();
- const metaMap = new Map<string, TickerMeta>();
- for (const t of initialTickers) {
- tickerMap.set(t.market, restToTickerData(t));
- metaMap.set(t.market, extractMeta(t));
- }
- tickersRef.current = tickerMap;
- metaRef.current = metaMap;
- setTickers(new Map(tickerMap));
- setMeta(new Map(metaMap));
- initializedRef.current = true;
- } else {
- loadTickers(quoteMarket);
- }
- }, [quoteMarket]);
- const loadTickers = async (quote: string) => {
- try {
- const res = await fetchApi<TickerRestData[]>(`/api/crypto/tickers?quote=${quote}`);
- if (res.success && res.data) {
- const tickerMap = new Map<string, TickerData>();
- const metaMap = new Map<string, TickerMeta>();
- for (const t of res.data) {
- tickerMap.set(t.market, restToTickerData(t));
- metaMap.set(t.market, extractMeta(t));
- }
- tickersRef.current = tickerMap;
- metaRef.current = metaMap;
- setTickers(new Map(tickerMap));
- setMeta(new Map(metaMap));
- initializedRef.current = true;
- }
- } catch (error) {
- console.error('Failed to load tickers:', error);
- }
- };
- // 배치 업데이트 (250ms 간격)
- const scheduleUpdate = useCallback(() => {
- if (updateTimerRef.current) {
- return;
- }
- updateTimerRef.current = setTimeout(() => {
- setTickers(new Map(tickersRef.current));
- updateTimerRef.current = null;
- }, 250);
- }, []);
- // SignalR 구독
- useEffect(() => {
- if (!cryptoConnection || !cryptoConnected) {
- return;
- }
- const handleTicker = (ticker: TickerData) => {
- tickersRef.current.set(ticker.market, ticker);
- scheduleUpdate();
- };
- const handleTickers = (list: TickerData[]) => {
- for (const ticker of list) {
- tickersRef.current.set(ticker.market, ticker);
- }
- scheduleUpdate();
- };
- // 이전 구독 해제
- if (subscribedQuoteRef.current && subscribedQuoteRef.current !== quoteMarket) {
- cryptoConnection.invoke('UnsubscribeTickers', subscribedQuoteRef.current).catch(() => {});
- }
- cryptoConnection.on('ReceiveTicker', handleTicker);
- cryptoConnection.on('ReceiveTickers', handleTickers);
- cryptoConnection.invoke('SubscribeTickers', quoteMarket).catch(console.error);
- subscribedQuoteRef.current = quoteMarket;
- return () => {
- cryptoConnection.off('ReceiveTicker', handleTicker);
- cryptoConnection.off('ReceiveTickers', handleTickers);
- if (subscribedQuoteRef.current) {
- cryptoConnection.invoke('UnsubscribeTickers', subscribedQuoteRef.current).catch(() => {});
- subscribedQuoteRef.current = null;
- }
- if (updateTimerRef.current) {
- clearTimeout(updateTimerRef.current);
- updateTimerRef.current = null;
- }
- };
- }, [cryptoConnection, cryptoConnected, quoteMarket, scheduleUpdate]);
- return { tickers, meta };
- }
|