React 18 beta의 Automatic Batching 동작 확인
배경
React 18 beta가 공개되면서 주요 변경사항 중 하나인 automatic batching을 실제로 테스트해볼 필요가 있었다. 기존 프로젝트에 영향을 줄 수 있는 부분이라 미리 파악하고자 했다.
기존 동작 (React 17)
React 17까지는 이벤트 핸들러 내부에서만 배칭이 동작했다. setTimeout이나 Promise 내부에서는 각 setState마다 리렌더링이 발생했다.
function Counter() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
// 배칭됨 - 1번 렌더
setCount(c => c + 1);
setFlag(f => !f);
setTimeout(() => {
// 배칭 안됨 - 2번 렌더 (React 17)
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);
}
}
React 18의 변경점
React 18에서는 모든 상황에서 자동으로 배칭이 적용된다. createRoot를 사용하면 활성화된다.
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
이제 setTimeout, Promise, native event handler 등 어디서든 배칭이 동작한다.
테스트 결과
실제로 렌더링 횟수를 측정해봤다.
function TestComponent() {
const [count, setCount] = useState(0);
const renderCount = useRef(0);
renderCount.current++;
console.log('Render count:', renderCount.current);
const handleAsyncUpdate = async () => {
const data = await fetchData();
setCount(data.count); // React 17: 렌더
setFlag(data.flag); // React 17: 렌더
// React 18: 1번만 렌더
};
}
Promise 내부에서도 배칭이 적용되어 렌더링 횟수가 줄어드는 것을 확인했다.
배칭을 원하지 않는 경우
의도적으로 배칭을 막아야 한다면 flushSync를 사용할 수 있다.
import { flushSync } from 'react-dom';
flushSync(() => {
setCount(c => c + 1);
});
// DOM 업데이트 완료
flushSync(() => {
setFlag(f => !f);
});
결론
React 18의 automatic batching은 성능 개선에 도움이 되지만, 기존 코드에서 렌더링 타이밍에 의존하는 부분이 있다면 점검이 필요하다. 대부분의 경우 문제없이 동작했지만, flushSync의 존재를 알아두면 유용할 것 같다.