66 lines
1.5 KiB
TypeScript
66 lines
1.5 KiB
TypeScript
|
|
import { memo, useState, useEffect } from 'react';
|
||
|
|
import type { ReactNode } from 'react';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 차트 지연 로딩을 위한 래퍼 컴포넌트
|
||
|
|
* 스켈레톤 UI 표시 후 차트 렌더링
|
||
|
|
*/
|
||
|
|
interface ChartWrapperProps {
|
||
|
|
children: ReactNode;
|
||
|
|
delay?: number;
|
||
|
|
height?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const ChartWrapper = memo(function ChartWrapper({
|
||
|
|
children,
|
||
|
|
delay = 100,
|
||
|
|
height = 300
|
||
|
|
}: ChartWrapperProps) {
|
||
|
|
const [showChart, setShowChart] = useState(false);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const timer = setTimeout(() => setShowChart(true), delay);
|
||
|
|
return () => clearTimeout(timer);
|
||
|
|
}, [delay]);
|
||
|
|
|
||
|
|
if (!showChart) {
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className="w-full animate-pulse bg-muted rounded-lg"
|
||
|
|
style={{ height: `${height}px` }}
|
||
|
|
>
|
||
|
|
<div className="h-full flex items-center justify-center">
|
||
|
|
<div className="text-muted-foreground text-sm">차트 로딩 중...</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return <>{children}</>;
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 메모이제이션된 차트 컴포넌트 래퍼
|
||
|
|
*/
|
||
|
|
interface OptimizedChartProps {
|
||
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
|
|
data: any;
|
||
|
|
children: ReactNode;
|
||
|
|
height?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const OptimizedChart = memo(function OptimizedChart({
|
||
|
|
data: _data,
|
||
|
|
children,
|
||
|
|
height = 300
|
||
|
|
}: OptimizedChartProps) {
|
||
|
|
return (
|
||
|
|
<ChartWrapper height={height}>
|
||
|
|
{children}
|
||
|
|
</ChartWrapper>
|
||
|
|
);
|
||
|
|
}, (prevProps, nextProps) => {
|
||
|
|
// 데이터가 같으면 리렌더링 스킵
|
||
|
|
return prevProps.data === nextProps.data;
|
||
|
|
});
|