React 18 Concurrent Rendering 도입 후기

배경

회사 대시보드 프로젝트가 React 17.0.2를 사용 중이었다. 데이터 테이블이 많아 렌더링 성능이 이슈였고, React 18의 Concurrent Features가 도움이 될 것 같아 업그레이드를 진행했다.

마이그레이션 과정

1. ReactDOM.render → createRoot

가장 기본적인 변경사항이다.

// Before
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));

// After
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

2. Automatic Batching 영향

React 18부터는 Promise, setTimeout 내부에서도 자동으로 배칭이 된다. 기존 코드 중 일부에서 의도적으로 여러 번 렌더링을 유발하던 패턴이 있었는데, 이 부분을 flushSync로 감싸서 해결했다.

import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1);
});
setFlag(true); // 별도 렌더링

3. useTransition 활용

검색 필터링이 느린 페이지에 useTransition을 적용했다.

const [isPending, startTransition] = useTransition();

const handleSearch = (query) => {
  setQuery(query); // 즉시 반영
  startTransition(() => {
    setFilteredResults(expensiveFilter(query)); // 지연 가능
  });
};

체감상 입력 반응성이 확실히 개선됐다.

주의사항

  • Suspense는 아직 data fetching에 공식 지원이 없어 스킵했다
  • React Testing Library도 함께 업데이트 필요 (12.x)
  • StrictMode에서 useEffect가 두 번 실행되는 건 개발 모드 전용이다

결과

번들 크기는 거의 동일했고, 주요 페이지의 INP(Interaction to Next Paint)가 평균 15% 개선됐다. 큰 breaking change 없이 점진적으로 도입할 수 있어서 만족스러웠다.