React 18 알파 버전 - Automatic Batching 살펴보기

배경

어제 React 18 Working Group에서 알파 버전이 공개되었다. 가장 눈에 띄는 변경사항 중 하나가 Automatic Batching이었다. 지금까지는 React 이벤트 핸들러 내부에서만 setState가 자동으로 배칭되었는데, 이제는 Promise, setTimeout 내부에서도 배칭이 적용된다고 한다.

기존 동작 방식

React 17까지는 이런 코드가 두 번의 렌더링을 발생시켰다.

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // 각각 렌더링 발생
}, 1000);

반면 이벤트 핸들러 내부에서는 배칭이 되어 한 번만 렌더링되었다.

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // 한 번만 렌더링
}

React 18의 변화

알파 버전을 설치해서 테스트해봤다. createRoot API를 사용하면 모든 경우에 자동 배칭이 적용되었다.

import { createRoot } from 'react-dom/client';

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

이제 setTimeout, Promise, 네이티브 이벤트 핸들러 어디에서든 여러 setState를 호출해도 한 번만 렌더링된다.

성능 측정

실제 대시보드 프로젝트에서 API 응답 후 여러 상태를 업데이트하는 부분에 적용해봤다. React DevTools Profiler로 측정한 결과 렌더링 횟수가 확실히 줄어들었다.

fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    setData(data);
    setLoading(false);
    setError(null);
    // React 17: 3번 렌더링
    // React 18: 1번 렌더링
  });

주의사항

만약 배칭을 원하지 않는 경우 flushSync를 사용하면 된다.

import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1);
});
// 즉시 렌더링
setFlag(f => !f);

아직 알파 버전이라 프로덕션에 적용하기는 이르지만, Concurrent 기능들과 함께 정식 릴리즈되면 유용할 것 같다.

React 18 알파 버전 - Automatic Batching 살펴보기