내가 담당한 빗썸에서 제공하는 api 응답값과 다른 팀원이 담당한 업비트 api의 응답값의 데이터 형식이 달라서 하나의 컴포넌트를 공유할 수 없는 상황

위 사진에 보이는 암호화폐의 실시간 가격 변동을 보여주는 테이블을 구현하는 과정에서 빗썸의 데이터 형식과 업비트의 데이터 형식이 달라서 공통 형식으로 통일하지 않으면 컴포넌트에서 데이터 처리가 복잡해지는 문제가 발생했다.
각 거래소(빗썸, 업비트)마다 컴포넌트를 각자 만들어 각 데이터 형식을 그대로 사용한다.
데이터 형식을 하나로 통일하여 하나의 컴포넌트를 공유하여 사용한다.
해결 방안 1은 중복된 코드가 너무 많이 발생할 수 있다는 점과 유지보수의 어려움이 발생할 수 있다는 점 때문에 적절하지 않다고 생각했다.
따라서 데이터 형식을 업비트의 데이터 형식을 기준으로 빗썸의 데이터 형식을 전처리 하는 기능을 하는 커스텀 훅을 만드는 방식으로 해결했다.
데이터 형식을 통일하여 하나의 컴포넌트를 공유하며 재사용하는 방식이 컴포넌트 본래의 의미에도 맞고 효율적인 방식이라고 생각되어 두번째 방안을 선택했다. 또한, 이는 기존에 사용하던 가격 변동 표에 수정 사항이 생기더라도 유지보수성에 잠정이 있다고 생각되었다.
이 과정에서 타입스크립트의 장점을 살려 해결할 수 이었다. 미리 통일할 데이터 타입을 interface로 정해두고 이에 맞게 데이터를 전처리하니 놓칠 수 있었던 버그를 예방하고 후에 에러가 발생했을 때 빠르게 문제를 해결할 수 있었다.
import { useRef, useState, useEffect } from 'react';
import { useBinanceTicker } from 'hooks/binance';
import { baseExchangeState, marketCodesState } from 'recoil/atoms/commonAtoms';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import type { ITicker } from '../@types/common.types';
import type { IUpbitMarketCode, IUpbitTicker } from '../@types/upbit.types';
import type { IBithumbWsTicker } from '../@types/bithumb.types';
import { updateSocketDataWithBinance } from './binance';
import {
changes,
changesRatio,
highRatio,
highest_52_week_rate,
lowRatio,
lowest_52_week_rate,
preprocessBithumbTicker,
replaceDate,
} from 'utils';
import { fetchUpbitMarketCode } from 'api/upbit/fetchUpbitMarketCode';
import { fetchBithumbTicker } from 'api/bithumb/fetchBithumbTicker';
export function useKoreanTicker() {
const baseExchange = useRecoilValue(baseExchangeState);
const setMarketCodes = useSetRecoilState(marketCodesState);
const { binanceTickers } = useBinanceTicker();
const [socketDatas, setSocketDatas] = useState<ITicker[]>([]);
const socket = useRef<WebSocket | null>(null);
const upbitWsTicker = async () => {
const marketCodes = await fetchUpbitMarketCode();
setMarketCodes(marketCodes.map((marketCode) => marketCode.market));
socket.current = new WebSocket('wss://api.upbit.com/websocket/v1');
socket.current.onopen = () => {
if (socket.current?.readyState == 1) {
socket.current.send(
JSON.stringify([
{ ticket: 'seungjun2' },
{
type: 'ticker',
codes: marketCodes.map((code: IUpbitMarketCode) => code.market),
},
{ format: 'DEFAULT' },
]),
);
}
};
socket.current.onmessage = (evt: MessageEvent) => {
if (evt.data instanceof Blob) {
const reader = new FileReader();
reader.onload = function () {
const blobData = reader.result;
try {
const parsedData: IUpbitTicker = JSON.parse(blobData as string);
const {
code,
trade_price,
opening_price,
signed_change_rate,
signed_change_price,
trade_date,
highest_52_week_price,
lowest_52_week_price,
acc_trade_price_24h,
} = parsedData;
setSocketDatas((prevList) => {
const existingIndex = prevList.findIndex(
(item) => item.symbol === parsedData?.code.split('-')[1],
);
if (existingIndex !== -1) {
return prevList.map((item, index) =>
index === existingIndex
? {
...item,
tradePrice: trade_price,
openingPrice: opening_price,
changeRatio: signed_change_rate * 100,
changePrice: signed_change_price,
highestRatio: highest_52_week_rate(
trade_price,
highest_52_week_price,
),
highestPrice: highest_52_week_price,
lowestRatio: lowest_52_week_rate(
trade_price,
lowest_52_week_price,
),
lowestPrice: lowest_52_week_price,
tradeValue_24H: acc_trade_price_24h,
date: replaceDate(trade_date),
}
: item,
);
} else {
return [
...prevList,
{
symbol: code.split('-')[1],
coinName: marketCodes.filter(
(marketCode: IUpbitMarketCode) =>
marketCode.market === code,
)[0].korean_name,
thumbnail: `https://static.upbit.com/logos/${
code.split('-')[1]
}.png`,
tradePrice: trade_price,
openingPrice: opening_price,
changeRatio: signed_change_rate * 100,
changePrice: signed_change_price,
highestRatio: highest_52_week_rate(
trade_price,
highest_52_week_price,
),
highestPrice: highest_52_week_price,
lowestRatio: lowest_52_week_rate(
trade_price,
lowest_52_week_price,
),
lowestPrice: lowest_52_week_price,
tradeValue_24H: acc_trade_price_24h,
date: replaceDate(trade_date),
},
];
}
});
} catch (error) {
console.error('JSON 데이터 파싱 오류:', error);
}
};
reader.readAsText(evt.data);
}
};
};
const bithumbWsTicker = async () => {
const { marketCodes, bithumbTicker } = await fetchBithumbTicker();
const preprocessedBithumbTicker =
await preprocessBithumbTicker(bithumbTicker);
setMarketCodes(marketCodes);
setSocketDatas(preprocessedBithumbTicker);
socket.current = new WebSocket('wss://pubwss.bithumb.com/pub/ws');
socket.current.onopen = () => {
if (socket.current?.readyState === 1) {
socket.current?.send(
JSON.stringify({
type: 'ticker',
symbols: marketCodes.map((x: string) => {
return x + '_KRW';
}),
tickTypes: ['MID', '24H'],
}),
);
}
};
socket.current.onmessage = async (e) => {
const data: { type: string; content: IBithumbWsTicker } =
await JSON.parse(e.data);
if (data.type === 'ticker') {
const {
tickType,
symbol,
closePrice,
lowPrice,
highPrice,
prevClosePrice,
value,
date,
} = data.content;
setSocketDatas((prevState) => {
const existingIndex = prevState.findIndex(
(item) => item.symbol === symbol.replace('_KRW', ''),
);
if (existingIndex !== -1) {
if (tickType === 'MID') {
prevState[existingIndex] = {
...prevState[existingIndex],
tradePrice: Number(closePrice),
changeRatio: changesRatio(closePrice, prevClosePrice),
changePrice: changes(closePrice, prevClosePrice),
highestRatio: highRatio(closePrice, highPrice),
highestPrice: Number(highPrice),
lowestRatio: lowRatio(closePrice, lowPrice),
lowestPrice: Number(lowPrice),
date: replaceDate(date),
};
return [...prevState];
} else {
prevState[existingIndex] = {
...prevState[existingIndex],
tradeValue_24H: Number(value),
};
return [...prevState];
}
}
return [...prevState];
});
}
};
};
useEffect(() => {
if (baseExchange === 'upbit') {
upbitWsTicker();
} else if (baseExchange === 'bithumb') {
bithumbWsTicker();
}
return () => {
if (socket.current?.readyState !== 0) {
socket.current?.close();
setSocketDatas([]);
setMarketCodes([]);
}
};
}, [baseExchange]);
useEffect(() => {
if (binanceTickers) {
const newList = updateSocketDataWithBinance(socketDatas, binanceTickers);
setSocketDatas(newList);
}
}, [binanceTickers]);
return socketDatas;
}