React 18 Concurrent Rendering 도입 후기

배경

회사 어드민 프로젝트가 React 17.0.2를 사용 중이었는데, 데이터 테이블 렌더링 시 간헐적인 버벅임이 있었다. React 18의 Concurrent Features가 도움이 될 것 같아 업그레이드를 진행했다.

마이그레이션 과정

1. createRoot 전환

기존 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 확인

이전에는 setTimeout 내부의 setState가 배칭되지 않아 렌더링이 두 번 발생했다. React 18에서는 자동으로 배칭된다.

const handleClick = () => {
  setTimeout(() => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // React 17: 2번 렌더
    // React 18: 1번 렌더
  }, 1000);
};

3. useTransition 적용

필터링 로직에 useTransition을 적용해 입력 반응성을 개선했다.

const [isPending, startTransition] = useTransition();
const [filter, setFilter] = useState('');

const handleFilterChange = (e) => {
  startTransition(() => {
    setFilter(e.target.value);
  });
};

입력 필드는 즉시 반응하고, 무거운 리스트 렌더링은 지연되어 UX가 확실히 나아졌다.

이슈

  • React Testing Library 일부 테스트에서 act warning 발생. @testing-library/react를 13.3으로 업데이트해서 해결
  • Styled-components 5.3.5에서 hydration mismatch 경고. 아직 조사 중

결론

Breaking Change가 거의 없어서 반나절 만에 마이그레이션을 완료했다. Automatic Batching만으로도 불필요한 렌더링이 줄었고, useTransition은 특정 케이스에서 확실히 효과적이었다. Server Components는 아직 Next.js 12에서 알파 단계라 보류했다.