React 18 Concurrent Rendering 실무 적용기
배경
3월에 출시된 React 18을 5월 들어 프로덕션에 적용했다. 가장 관심이 갔던 부분은 Concurrent Rendering이었다. 특히 검색 필터가 많은 어드민 페이지에서 입력 지연이 문제였는데, useTransition으로 개선할 수 있을 것 같았다.
기존 문제
검색어 입력 시 약 2000개의 아이템을 필터링하는데, 매 키 입력마다 UI가 버벅였다. debounce를 적용했지만 입력 딜레이가 생기는 게 불만이었다.
const [query, setQuery] = useState('');
const filtered = items.filter(item =>
item.name.includes(query)
);
useTransition 적용
useTransition을 사용해 필터링을 낮은 우선순위로 처리했다.
const [query, setQuery] = useState('');
const [searchQuery, setSearchQuery] = useState('');
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value);
startTransition(() => {
setSearchQuery(e.target.value);
});
};
const filtered = items.filter(item =>
item.name.includes(searchQuery)
);
입력 필드는 즉시 반응하고, 필터링 결과는 약간 늦게 업데이트되지만 타이핑이 막히지 않았다. isPending으로 로딩 상태도 표시할 수 있었다.
useDeferredValue 대안
useDeferredValue로도 비슷하게 구현 가능했다.
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const filtered = items.filter(item =>
item.name.includes(deferredQuery)
);
코드는 더 간결하지만 pending 상태를 직접 제어할 수 없어서 useTransition을 선택했다.
결과
체감 성능이 확실히 개선됐다. debounce 없이도 입력이 부드럽고, 사용자 피드백도 긍정적이었다. 다만 모든 상황에 적합한 건 아니고, 실제로 렌더링 비용이 큰 경우에만 효과가 있었다.
React 18의 다른 기능들(Suspense SSR, Automatic Batching)도 점진적으로 적용할 예정이다.