React 18의 Automatic Batching과 성능 개선

배경

3월 말 React 18이 정식 릴리즈되어 회사 프로젝트에 적용해봤다. 가장 눈에 띄는 변화는 Automatic Batching이었다.

기존 문제

React 17까지는 이벤트 핸들러 내부에서만 batching이 동작했다. setTimeout이나 Promise 내부의 상태 업데이트는 각각 리렌더링을 발생시켰다.

// React 17: 2번 렌더링
fetch('/api/user').then(data => {
  setUser(data.user);      // 렌더링 1
  setLoading(false);       // 렌더링 2
});

이를 해결하려고 unstable_batchedUpdates를 사용했는데, 이름부터 불안정해 보였다.

React 18의 변화

React 18에서는 모든 상황에서 자동으로 batching이 적용된다.

// React 18: 1번 렌더링
fetch('/api/user').then(data => {
  setUser(data.user);
  setLoading(false);
  // 자동으로 batching됨
});

실제 대시보드 페이지에서 여러 API를 호출하는 부분에 적용했더니 불필요한 리렌더링이 현저히 줄어들었다.

주의사항

만약 의도적으로 동기적 업데이트가 필요하다면 flushSync를 사용할 수 있다.

import { flushSync } from 'react-dom';

flushSync(() => {
  setCounter(c => c + 1);
});
// 즉시 렌더링됨

마이그레이션

업그레이드는 생각보다 순조로웠다. ReactDOM.rendercreateRoot로 변경하는 정도였고, 기존 코드는 대부분 그대로 동작했다.

// Before
ReactDOM.render(<App />, document.getElementById('root'));

// After
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Concurrent Features는 아직 안정화될 때까지 기다려볼 예정이다.