Batching이란?
React가 성능상의 이점을 누리기 위해 여러 개의 state 업데이트를 한 번의 리렌더링에 묶어서 수행하는 것을 말한다. React 17까지는 이벤트 핸들러에서만 batching이 일어났다.
// Before React 18 only React events were batched
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);
React는 handleClick
함수가 이벤트 핸들러인지 어떻게 알까?
<button onClick={handleClick} />
라는 코드를 보자. React는 JSX에서 정의된 onClick 이벤트 핸들러를 감지하고, Synthetic Event 시스템에 등록한다. Synthetic Event 시스템을 통해 호출된 함수는 이벤트 핸들러로 작동한다. (따라서 handleClick 함수는 이벤트 핸들러가 된다)
그 후에는 다음과 같은 과정이 수행된다.
사용자가 버튼을 클릭하면, React는 브라우저 네이티브 이벤트를 감지하여 Synthetic Event 객체로 변환한다.(아래의 e가 synthetic event 객체이다)
<button onClick={e => { console.log(e); // React event object (synthetic event object) }} />
변환된 Synthetic Event 객체를
handleClick
함수에 전달해 호출한다.handleClick
함수 내부에서 발생하는 모든 상태 업데이트를 수집한다.handleClick
함수가 종료되면, 수집된 모든 상태 업데이트를 한 번의 렌더링으로 처리한다.
그럼 onClick만 붙이면 모든게 batching 될까?
Dan이 작성한 데모를 보자. Demo: React 17 does NOT batch outside event handlers
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 17 and earlier does NOT batch these because
// they run *after* the event in a callback, not *during* it
setCount(c => c + 1); // Causes a re-render
setFlag(f => !f); // Causes a re-render
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
여기서 중요한 점은 handleClick은 앞서 설명한 synthetic Event로 등록이 되었지만, setCount와 setFlag가 해당 함수가 동작하는 동안 실행되는게 아니라 handleClick이 끝나고 callback으로 실행되기 때문에 batching이 일어나지 않는다는 점이다(handleClick 이벤트 핸들러 외부에서 발생하는 것으로 취급됨)
React 18에서는 이런 것들도 batching됨을 확인할 수 있다: Demo: React 18 with createRoot
batches even outside event handlers!
이것이 automatic batching이다.
Automatic Batching
React 18에서는 사용자가 별도로 추가적인 설정을 하지 않아도 기본값으로 batching을 더 많이 하게 된다. React 18부터 createRoot
를 사용하면, 업데이트가 어디서 발생하든 모든 업데이트가 자동으로 batching 된다.
이는 불필요한 리렌더링을 막기 때문에 성능 향상에 도움이 된다.
// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
automatic batching을 사용하지 않고 싶다면
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
flushSync
를 사용하여 automatic batching을 비활성화할 수 있다.
- 첫 번째
flushSync
내부에서setCount
를 호출하여 상태 업데이트 후 리렌더링 하여 DOM에 적용한다. - 그 후 두 번째
flushSync
내부에서setFlag
를 호출하여 상태 업데이트 후 리렌더링 하여 DOM에 적용한다. 이는 React 18이전에 사용하듯이 (이벤트 핸들러를 제외하고) Automatic batching을 적용하지 않은 상태와 같다.
참고자료
'React' 카테고리의 다른 글
리액트 컴파일러 (React Compiler) 알아보기 (2) | 2024.04.14 |
---|---|
ref 와 state 차이 (0) | 2023.03.04 |