React의 state를 변경시키는 것은 useState
이다. state가 변경되면 컴포넌트는 리렌더링되는데, 이 때 React는 이를 비동기로 동작시킨다.
function App() {
const [count, setCount] = useState(0);ㅁ
const [flag, setFlag] = useState(false);
function handleClick() {
setCount((c) => c + 1); // 아직 리렌더링 하지 않는다
setFlag((f) => !f); // 아직 리렌더링 하지 않는다
// React는 이 함수가 끝나면 리렌더링을 한다.
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style=>{count}</h1>
</div>
);
}
위의 컴포넌트에서 button
을 클릭하게되면 두 번의 state변경이 있었기 때문에, 렌더링도 두 번 일어날 것 같지만 사실은 함수 내에 있는 업데이트 함수 두 개가 모두 실행된 후에 한 번의 리렌더링만 일어나게 된다. 이를 React의 batching(배칭, 일괄처리)라고 한다. batching은 불필요한 리렌더링을 줄여주기 때문에 성능에 크게 도움을 준다.
batching은 이벤트 핸들러(함수)내에서 또는 메서드 내에서 동작한다. 따라서 같은 함수 안에 있는 업데이트가 한 번에 리렌더링 되고, useEffect
와 같은 메서드 내에서 있는 업데이트가 모여 한 번에 리렌더링 된다.
하지만 모든 상황에서 batching이 실행되는 것은 아니다. 함수가 비동기처리 방식으로 실행되는 경우, async/await, then/catch, setTimeout, fetch 등의 경우에는 일괄적으로 처리되지 않는다.
function handleClick() {
fetchSomething().then(() => {
setCount((c) => c + 1); // 리렌더링을 발생시킨다.
setFlag((f) => !f); // 리렌더링을 발생시킨다.
});
}
첫 번째 코드와 유사한 상황이지만 위의 경우 then을 통해 이벤트 핸들링이 완료된 후에 state를 업데이트가 이루어지기 때문에 batching은 이루어지지 않고, 두 번의 리렌더링을 발생시키게 된다.
비동기 처리로 batching이 이뤄지지 않는 경우에도 강제로 적용시키는 방법이 있는데 바로 ReactDOM.unstable_batchedUpdates를 사용하는 것이다.
import ReactDOM from "react-dom";
function handleClick() {
fetchSomething().then(() => {
ReactDOM.unstable_batchedUpdates(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
}
});
}
이렇게 위와 같이 작성하는 경우 batching을 강제로 적용시킬 수 있다.
자동 배칭은 React 18버전에서 부터 *createRoot
를 통해 적용된 것으로, 모든 업데이트가 어디에 포함되어 있는가와 관계없이 자동으로 batching 처리되도록 변경된 것을 말한다.
ReactDOM.unstable_batchedUpdates
를 사용하지 않고도 모든 경우에 batching을 자동으로 처리해주기 때문에 당장 없어진 것은 아니지만 추후 버전 업데이트에서 사라질 가능성이 높아보인다.
또한 batching을 적용하고 싶지 않은 경우에는 ReactDOM.flushSync()
를 사용하여 배칭을 하지 않도록 막는 것도 가능하다.
Reference
* createRoot : React 18 업데이트(번역글)