React 18 Concurrent Rendering으로 대용량 리스트 최적화
문제 상황
사내 관리자 대시보드에서 5000개 이상의 주문 데이터를 테이블로 보여주는 화면이 있었다. 검색 필터를 입력할 때마다 UI가 버벅이고, 입력 지연이 심해 사용성이 떨어진다는 피드백을 받았다.
React 18 적용
4월에 정식 릴리즈된 React 18을 도입했다. 기존 ReactDOM.render를 createRoot로 변경하고 Concurrent Features를 적용했다.
import { useTransition, useDeferredValue } from 'react';
function OrderDashboard() {
const [searchTerm, setSearchTerm] = useState('');
const [isPending, startTransition] = useTransition();
const deferredSearchTerm = useDeferredValue(searchTerm);
const handleSearch = (e) => {
const value = e.target.value;
setSearchTerm(value); // 즉시 업데이트
startTransition(() => {
// 무거운 필터링 작업은 낮은 우선순위로
});
};
const filteredOrders = orders.filter(order =>
order.id.includes(deferredSearchTerm)
);
return (
<div>
<input value={searchTerm} onChange={handleSearch} />
{isPending && <Spinner />}
<OrderTable data={filteredOrders} />
</div>
);
}
결과
useTransition을 통해 입력은 즉시 반응하고, 리스트 렌더링은 낮은 우선순위로 처리되면서 체감 성능이 크게 개선됐다. 사용자는 타이핑이 막히지 않고, isPending 상태로 로딩 표시도 자연스럽게 처리할 수 있었다.
추가로 virtualized 라이브러리(react-window) 적용도 검토 중이지만, 일단 Concurrent Features만으로도 충분한 개선 효과를 얻었다.
주의사항
- Suspense와 함께 사용하면 더 강력하지만, 기존 코드베이스에 단계적으로 적용하는 게 안전했다
- useTransition의 isPending은 최소 지연 시간이 있어서 너무 빠른 작업은 스피너가 깜빡일 수 있다
- React DevTools Profiler로 렌더링 우선순위를 확인하며 작업했다