대시보드 중간 커밋
This commit is contained in:
115
features/dashboard/hooks/useOrderBook.ts
Normal file
115
features/dashboard/hooks/useOrderBook.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { KisRuntimeCredentials } from "@/features/dashboard/store/use-kis-runtime-store";
|
||||
import type { DashboardStockOrderBookResponse } from "@/features/dashboard/types/dashboard.types";
|
||||
import { fetchStockOrderBook } from "@/features/dashboard/apis/kis-stock.api";
|
||||
import { useKisOrderbookWebSocket } from "@/features/dashboard/hooks/useKisOrderbookWebSocket";
|
||||
import { toast } from "sonner";
|
||||
|
||||
/**
|
||||
* @description 선택한 종목의 초기 호가를 조회하고 실시간 호가로 갱신합니다.
|
||||
* @see features/dashboard/components/orderbook/OrderBook.tsx 호가창 화면에서 사용합니다.
|
||||
*/
|
||||
export function useOrderBook(
|
||||
symbol: string | undefined,
|
||||
market: "KOSPI" | "KOSDAQ" | undefined,
|
||||
credentials: KisRuntimeCredentials | null,
|
||||
isVerified: boolean,
|
||||
options: { enabled?: boolean } = {},
|
||||
) {
|
||||
const { enabled = true } = options;
|
||||
const isRequestEnabled = enabled && !!symbol && !!credentials;
|
||||
const requestSeqRef = useRef(0);
|
||||
const lastErrorToastRef = useRef<string>("");
|
||||
|
||||
const [initialData, setInitialData] =
|
||||
useState<DashboardStockOrderBookResponse | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRequestEnabled || !symbol || !credentials) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestSeq = ++requestSeqRef.current;
|
||||
let isDisposed = false;
|
||||
|
||||
const loadInitialOrderBook = async () => {
|
||||
setInitialData(null);
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const data = await fetchStockOrderBook(symbol, credentials);
|
||||
if (isDisposed || requestSeq !== requestSeqRef.current) return;
|
||||
setInitialData(data);
|
||||
} catch (err) {
|
||||
if (isDisposed || requestSeq !== requestSeqRef.current) return;
|
||||
console.error("Failed to fetch initial orderbook:", err);
|
||||
const message =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "호가 정보를 불러오지 못했습니다. 잠시 후 다시 시도해 주세요.";
|
||||
setError(message);
|
||||
if (lastErrorToastRef.current !== message) {
|
||||
lastErrorToastRef.current = message;
|
||||
toast.error(message);
|
||||
}
|
||||
} finally {
|
||||
if (isDisposed || requestSeq !== requestSeqRef.current) return;
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void loadInitialOrderBook();
|
||||
|
||||
return () => {
|
||||
isDisposed = true;
|
||||
};
|
||||
}, [isRequestEnabled, symbol, credentials]);
|
||||
|
||||
const {
|
||||
realtimeOrderBook,
|
||||
isConnected: isWsConnected,
|
||||
error: wsError,
|
||||
} = useKisOrderbookWebSocket(
|
||||
symbol,
|
||||
market,
|
||||
credentials,
|
||||
isVerified && enabled,
|
||||
);
|
||||
|
||||
// 실시간 패킷이 0값으로 들어오는 경우에는 초기 REST 호가를 유지합니다.
|
||||
const safeRealtimeOrderBook = isUsableOrderBook(realtimeOrderBook)
|
||||
? realtimeOrderBook
|
||||
: null;
|
||||
const orderBook = isRequestEnabled
|
||||
? (safeRealtimeOrderBook ?? initialData)
|
||||
: null;
|
||||
const mergedError = isRequestEnabled ? error || wsError : null;
|
||||
const mergedLoading = isRequestEnabled ? isLoading && !orderBook : false;
|
||||
|
||||
return {
|
||||
orderBook,
|
||||
isLoading: mergedLoading,
|
||||
error: mergedError,
|
||||
isWsConnected,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 호가 데이터가 실제 유효한지(0만 있는지) 판별합니다.
|
||||
* @see features/dashboard/hooks/useOrderBook.ts 실시간/초기 호가 병합 우선순위 결정에 사용합니다.
|
||||
*/
|
||||
function isUsableOrderBook(orderBook: DashboardStockOrderBookResponse | null) {
|
||||
if (!orderBook) return false;
|
||||
if (orderBook.totalAskSize > 0 || orderBook.totalBidSize > 0) return true;
|
||||
|
||||
return orderBook.levels.some(
|
||||
(level) =>
|
||||
level.askPrice > 0 ||
|
||||
level.bidPrice > 0 ||
|
||||
level.askSize > 0 ||
|
||||
level.bidSize > 0,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user