[Automatic Batching] 리액트 18에 등장한 렌더링 묶음판매
Automatic Batching이란 무엇일까?
[[React]] 18에서 나온 새로운 기능, Automatic Batching에 대한 문서이다
여기서 Batching이란, 이벤트 핸들러나 hooks 안에서 상태 업데이트를 묶어서 동작하도록 만들어준다. 즉, 렌더링을 한 번만 일어나도록 해주는 것이다
따라서 Automatic Batching
= 자동화된 일괄 렌더링
이라고 생각할 수 있다
사실 생각해보면 이상하다. 기존의 함수 내부에서 여러 setState()를 실행시키면 이미 Batching이 이루어졌을까?
- 그 이유는 React에는 여러 번의
state update
작업을Queue
에 몰아넣고 일정 주기마다Queue
에 등록된 작업을 순차적으로 일괄 시행하면서 불필요한 리렌더링을 방지하는 특징이 있기 때문이다
코드를 통해 확인해보자
이전에는 batching
이 각각의 React event
마다 한 번씩 이루어졌었다
// Before: only React events were batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will render twice, once for each state update (no batching)
}, 1000);
결국 위 코드에서는 각각의 이벤트들마다 렌더링이 되므로, 두 번 렌더링 되었다
setCount()
에서 한 번setFlag()
에서 한 번 더
이는 불필요한 렌더링을 야기한다는 점에서 문제를 일으킨다
기존(~React 17)에는 오직 React의 이벤트 핸들러 내부의 state update 작업에 대해서만 batching
이 가능했다. 이 말은 Promise
, setTImeout
, native event handler
내부의 작업은 불가능했었다
React 18버전으로 넘어가면서 리액트 이벤트 핸들러
내부 뿐만 아니라 promises
, setTimeout
, 여러 native event handler
안에서도 배칭이 적용된다
// After: updates inside of timeouts, promises,
// native event handlers or any other event are batched.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
이렇게 되면 setTimeout()
안에있는 내용들에 Automatic Batching
이 적용되면서 렌더링을 한번만 발생시킨다
발동조건
하지만 언제나 이렇게 되는 것은 아니라는 것을 주의해야 한다
.createRoot() method
를 사용했을때만automatic batching
이 적용된다- 만약 이전 레거시인
.render() method
를 사용할 경우 React 18이어도 적용되지 않는다
이 설정을 적용해서 사용하려면?
App.js 와 이를 설정하는 index.js 두 파일로 설명을 해보겠다
App.js
import React from 'react';
export default function App() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase Twice</button>
</div>
);
};
.createRoot() 사용 시 Automatic Batching 적용
추가로 [[React Concurrent Mode]]의 기능을 활용할 수 있다
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
const root = document.getElementById('root');
const reactRoot = ReactDOM.createRoot(root);
reactRoot.render(<App />);
.render() 사용 시 Automatic Batching 적용
단, React 18버전 이상일 경우 이는 적용되지 않는다
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
+ 만약 Automatic Batching을 무시해야 하는 상황이라면?
react-dom 라이브러리에 추가된 ReactDOM.flushSync()
메서드를 사용한다
- 단, React에서는 공식적으로 해당 메서드의 사용을 추천하지는 않는다
- 필요한 상황이 있을 경우에만 사용할 것을 권장하고 있다
아래에는 이 메서드를 활용한 렌더링 확인용 Counter와 렌더링이 될 때마다 로그를 찍도록 하는 간단한 예제를 만들어보았다
// App.js
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
export default function App() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
ReactDOM.flushSync(() => { // 여기서 첫 렌더링
setCount(prev => prev + 1);
});
setCount(prev => prev + 1); // 이 이후 일괄 적용 렌더링
setCount(prev => prev + 1);
};
useEffect(() => {
console.log('Component rendered with count:', count);
});
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase Thrice</button>
</div>
);
};
실제로 확인해 볼 경우 .flushSync()
가 적용되어있는 경우에는 두 번씩 렌더링되며, 이 메서드를 빼고 확인해볼 경우에 한 번씩 렌더링되는 것을 볼 수 있다!
References
- https://react.dev/blog/2022/03/29/react-v18
- https://velog.io/@dbwjd5864/React-18-automatic-batching%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90#:~:text=automatic%20batching%2C%20%EC%9E%90%EB%8F%99%20%EB%B0%B0%EC%B9%AD%EC%9D%B4%EB%9E%80,%EC%95%88%EC%97%90%EC%84%9C%EB%8F%84%20%EB%B0%B0%EC%B9%AD%EC%9D%B4%20%EC%A0%81%EC%9A%A9%EB%90%9C%EB%8B%A4.
- https://velog.io/@rookieand/React-18%EC%97%90%EC%84%9C-%EC%B6%94%EA%B0%80%EB%90%9C-Auto-Batching-%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80