๐ซ ์ฌ์ฉ์์ ์์ ๋ณํ ์๋ ๊ทธ๋ํ ์คํฌ๋กค ๊ตฌํํ๊ธฐ - boostcampwm-2024/web17-juchumjuchum GitHub Wiki
๋ถ์ผ | ์์ฑ์ | ์์ฑ์ผ |
---|---|---|
FE | ์กฐ๋ฐฐ๊ฒฝ | 24๋ 12์ 03์ผ |
์ด์ ์ผ๋ก ์คํฌ๋กคํ๋ฉฐ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๋ฐ์์ฌ ๋, ๋ถ๋๋ฝ๊ฒ ์ด์ด์ง์ง ์๊ณ ๊ทธ๋ํ์ ์์น๊ฐ ๋ณํํ๋ค.
์์ธ์ ๊ทธ๋ํ ์์ฑ๊ณผ ๊ทธ๋ํ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ ๋ก์ง์ด ํ๋์ useEffect์์ ๋ค์ด๊ฐ์๋๋ฐ, ์์กด์ฑ ๋ฐฐ์ด์ ๊ทธ๋ํ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋์ด ์๋ค. ์ฆ ๋ฐ์ดํฐ๊ฐ ๋ณํํ๋ฉด ๊ทธ๋ํ๋ฅผ ๋ค์ ์์ฑํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ฒ์ด๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๊ทธ๋ํ๋ฅผ ํ ๋ฒ๋ง ์์ฑํ๊ณ , ๋ฐ์ดํฐ๋ ๊ณ์ํด์ ์ ๋ฐ์ดํธํ๋๋ก ๋ก์ง์ ๋ณ๊ฒฝํ๋ค.
ํ๋์ useEffect์์ ๊ทธ๋ํ ์์ฑ, ํ ๋ง ๋ณ๊ฒฝ, ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ๊ฐ ์ ๋ถ ์ด๋ค์ก๋๋ฐ ์ด๋ฅผ ๊ฐ๊ฐ useEffect๋ก ๋๋ด๋ค.
๊ธฐ์กด ์ฝ๋
interface UseChartProps {
containerRef: RefObject<HTMLDivElement>;
priceData: StockTimeSeriesResponse['priceDtoList'];
volumeData: StockTimeSeriesResponse['volumeDtoList'];
}
const TransformPriceData = PriceSchema.transform((item) => ({
time: new Date(item.startTime).toISOString().slice(0, 10),
open: parseFloat(item.open),
high: parseFloat(item.high),
low: parseFloat(item.low),
close: parseFloat(item.close),
}));
const TransformVolumeData = VolumeSchema.transform((item) => ({
time: new Date(item.startTime).toISOString().slice(0, 10),
value: parseFloat(item.volume),
}));
export const useChart = ({
containerRef,
priceData,
volumeData,
}: UseChartProps) => {
const chart = useRef<IChartApi>();
const containerInstance = containerRef.current;
const { data: theme } = useGetUserTheme();
const graphTheme = theme === 'light' ? lightTheme : darkTheme;
useEffect(() => {
if (!containerInstance) return;
chart.current = createChart(containerInstance, {
width: containerInstance.clientWidth,
height: containerInstance.clientHeight,
...createChartOptions(graphTheme),
});
const volumeSeries = chart.current.addHistogramSeries(
createVolumeOptions(),
);
volumeSeries.priceScale().applyOptions({
scaleMargins: {
top: 0.93,
bottom: 0,
},
});
const transformedVolumeData = volumeData.map((item) =>
TransformVolumeData.parse(item),
);
const histogramData = getHistogramColorData(transformedVolumeData);
volumeSeries.setData(histogramData);
const candleSeries = chart.current.addCandlestickSeries(
createCandlestickOptions(graphTheme),
);
const transformedPriceData = priceData.map((item) =>
TransformPriceData.parse(item),
);
candleSeries.setData(transformedPriceData);
return () => {
chart.current?.remove();
};
}, [containerInstance, graphTheme, priceData, volumeData]);
return chart;
};
const chart = useRef<IChartApi>();
const candleSeries = useRef<ReturnType<IChartApi['addCandlestickSeries']>>();
const volumeSeries = useRef<ReturnType<IChartApi['addHistogramSeries']>>();
const containerInstance = containerRef.current;
๊ธฐ์กด ์ฝ๋์์๋ volume, candle ๊ทธ๋ํ๋ฅผ ์์๋ก ์์ฑํ์ฌ ์ปค์คํ ํด์คฌ๋๋ฐ, ๋ฆฌ๋ ๋๋ง ๋ฐฉ์ง๋ฅผ ์ํด useRef๋ก ์ ์ธํด์คฌ๋ค.
useEffect(() => {
if (!containerInstance) return;
// chart.current์๋ ์ฐจํธ ์์ฑ ๊ฐ ์ ์ฅ
chart.current = createChart(containerInstance, {
width: containerInstance.clientWidth,
height: containerInstance.clientHeight,
...createChartOptions(graphTheme),
});
// volumeSeries.current์๋ volume ์ฐจํธ ์์ฑ ๊ฐ ์ ์ฅ
volumeSeries.current = chart.current.addHistogramSeries(
createVolumeOptions(),
);
volumeSeries.current.priceScale().applyOptions({
scaleMargins: {
top: 0.93,
bottom: 0,
},
});
// candleSeries.current์๋ ์บ๋ค ์ฐจํธ ์์ฑ ๊ฐ ์ ์ฅ
candleSeries.current = chart.current.addCandlestickSeries(
createCandlestickOptions(graphTheme),
);
// unmount ๋๋ค๋ฉด ์ฐจํธ๋ฅผ remove ํด์ค๋ค.
return () => {
chart.current?.remove();
};
}, [containerInstance]);
-
.current
๋ useRef์ ์ฐธ์กฐ๊ฐ์ ์ ์ฅํ๊ณ ์ ๋ฐ์ดํธํ๋ ๊ณต๊ฐ์ด๋ค. - chart, volumeSeries, candleSeries์ ๊ฐ๊ฐ ์ฐจํธ๋ฅผ ์์ฑํ์ฌ ์ ์ฅํ๋ค.
ํ ๋ง๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒ์ ๋ค๋ฅธ ์ฌ์ดํด์ด๊ธฐ ๋๋ฌธ์ ๋ ๋ค๋ฅธ useEffect๋ก ์ฒ๋ฆฌํด์คฌ๋ค.
useEffect(() => {
if (!chart.current || !candleSeries.current) return;
// ์ฐจํธ์ candleSeries์ ํ
๋ง ์ ์ฉ
chart.current.applyOptions(createChartOptions(graphTheme));
candleSeries.current.applyOptions(createCandlestickOptions(graphTheme));
}, [graphTheme]);
volumeSeries๋ ๋ฐ์ดํฐ๋ฅผ ์ธํ ํ ๋ ๋ฐ๋ก color๋ฅผ ๋ฃ์ด์ฃผ๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ์ ๋ฐ๋ก ์ง์ ํ์ง ์๋๋ค.
useEffect(() => {
if (!candleSeries.current || !volumeSeries.current) return;
// api๋ก ๋ฐ์์ค๋ volumeData๋ฅผ ๊ทธ๋ํ ํ์์ ๋ง๊ฒ transform
const transformedVolumeData = volumeData.map((item) =>
TransformVolumeData.parse(item),
);
// api๋ก ๋ฐ์์ค๋ PriceData๋ฅผ ๊ทธ๋ํ ํ์์ ๋ง๊ฒ transform
const transformedPriceData = priceData.map((item) =>
TransformPriceData.parse(item),
);
// histogram์ ๋ฐ์ดํฐ์ color ๊ฐ ์ถ๊ฐ
const histogramData = getHistogramColorData(transformedVolumeData);
// candleSeries.current์ ๋ฐ์ดํฐ ์ ์ฅ
candleSeries.current.setData(transformedPriceData);
// volumeSeries.current์ ๋ฐ์ดํฐ ์ ์ฅ
volumeSeries.current.setData(histogramData);
}, [priceData, volumeData]);
- ์ฌ๊ธฐ์๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์ ๋ ์ ๋ฐ์ดํธํ๋ ๋ก์ง๋ง ๋ฌถ์ด๋๋ค.
์คํฌ๋กคํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ณ์ ๋ถ๋ฌ์๋ ๊ทธ๋ํ์ ํ์์น๊ฐ ๋ฐ๋์ง ์๋๋ค!