Batching이란 React 가 더 나은 성능을 위해 여러개의 state 업데이트를 한 번의 리렌더링으로 묶어서 진행하는 것을 말한다.
Automatic batching이 없다면 React의 이벤트 핸들러 내부에서만 batched 업데이트가 가능했다.
Batching의 이점
그럼 Batching이 일어나지 않을 경우 어떤 문제가 생길 수 있을까?
여러개의 state 업데이트가 동시에 일어나지 않을 때 발생할 수 있는 일을 생각해보자.
예를 들어 줄다리기. 한쪽 진영의 힘이 더 세지면 균형이 무너지기 마련이다.
이러한 점에서 teamA 와 teamB 라는 state를 통해서 각 진영의 힘의 크기를 나타냈다.
const [teamA, setTeamA] = useState(10);
const [teamB, setTeamB] = useState(10);
또한 힘의 균형이 무너지면 게임이 종료됨을 표현하기 위해 다음과 같이 useEffect를 정의했다.
useEffect(() => {
if(teamA !== teamB) navigator("/end")
},[teamA, teamB])
한 명 줄이기라는 버튼을 누르면 teamA와 teamB 각각의 값을 1씩 줄일 것이다.
React 의 이벤트 핸들러에서는 auto batching 을 지원하기에 Promise 의 콜백함수 안에 이 동작을 정의해서 batching이 발생하지 않도록 해보겠다.
const delay = (ms) => new Promise((resolve) => setTimeout(resolve,ms));
const decrease = async () => {
await delay(1500);
setTeamA((a) => a - 1);
setTeamB((b) => b - 1);
};
<button onClick={decrease}>한명 줄이기</button>
기대한 실행결과는 1500ms 이후에 teamA 와 teamB의 값이 1씩 감소한 모습일 것이다.
하지만 실제 결과는 게임이 종료되어버린다.
리렌더링 흐름
Auto Batching 이 적용되지 않는다면 이러한 상황처럼 의도치 않은 버그를 마주할 수도 있다.
React 에서는 이러한 상태를 컴포넌트의 업데이트가 “half-finished” 된 상태라고 정의한다.
우리가 의도한 로직 중 절반만 완성된 state에 의해 컴포넌트가 올바르게 동작하지 않는 상태를 말한다.
그러나 위에서의 상황 설명과 이론들은 결국 React 18 에서는 신경 쓰지 않아도 되는 부분이 되었다.
그 이유는 Automatic Batching 이 도입되었기 때문이다.
React18에서는 tiemout, promise, native 이벤트 핸들러 등 업데이트가 어느 곳에서 야기되었는지와 관계없이 Batching을 지원한다.
Automatic Batching을 사용하기 위해서는 React18에서 새롭게 등장한 ReactDOMClient의 CreateRoot 메서드를 사용해야 한다.
기존의 React의 entry 파일에서는 아래와 같이 정의하였다.
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
React18부터는 createRoot를 사용해 다음과 같이 정의하면 된다.
import {createRoot} from "react-dom/client";
const root = createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)