약 한 달전 데브코스 멘토님으로부터 리액트는 상태 변화같은게 실시간으로 적용되고 작동할까요? 라는 질문을 받았는데 그때 저는 대답을 하지 못했던 기억이 있었습니다. 물론 아닌것을 알고 있었지만, 왜? 어떤 기능 때문인지, 어떤 상황에서 하는지 잘 알지 못했기 때문에 혹시나 추가 질문이 들어올까... 무서워서 대답을 피했던적이 있습니다.
그러고 잠시 잊고 있다가 오늘 모던 리액트 딥다이브
책에서 해당 질문의 답이 담긴 이 10장 - 리액트 18에 추가된 기능 챕터의 자동 배치
부분을 보게 되었고 물론 지나간 일이지만, 만약 타임머신이 개발 된다면 그 질문 받았을 당시로 돌아가 당당히 대답할 수 있도록 오늘 책에서 공부했던 자동배치 부분과 책 내용 외에도 추가적으로 찾은 내용들과 함께 머릿속에도 그리고 포스팅으로도 정리를 진행했습니다
리액트가 여러 상태 업데이트를 하나의 리렌더링으로 묶는 것을 의미한다.
예를 들면, 한 번의 이벤트 발생으로 두 개 이상의 상태를 수정, 업데이트 해야한다고 했을 때, 이것을 하나의 리렌더링으로 묶어서 수행하게 기능입니다.
// 배치를 사용하지 않는다면?
function handleClick() {
setCounter((c) => c + 1) // 상태가 바뀌었으니 리렌더링..1번
setFlag((f) => f + 1) // 상태가 바뀌었으니 리렌더링..2번
setAge((f) => f + 1) // 상태가 바뀌었으니 리렌더링..3번?
}
리액트에서는 상태가 업데이트 된다면? 그 즉시 컴포넌트를 리렌더링시키는데 만약 위와 같은 함수를 실행한다면 총 3번의 렌더링을 하게 되지만, 리액트의 배치
가 등장 하고 나서부터는 변경할 상태들을 묶어서 적용한 후 한 번만 렌더링 하게 되기 때문에 성능 향상에 많은 도움이 된다!
// 리액트 배치 적용o
function handleClick() {
setCounter((c) => c + 1) // 일단 상태값만 바꿈
setFlag((f) => f + 1) // 일단 상태값만 바꿈2
setAge((f) => f + 1) // 상태 값 마저 바꾸고 render 함수 호출하고 리렌더링!
}
// 이렇게 여러 상태값이 바뀌어도 한번만 렌더링됨
Reconciliation
과정을 통해 이전 가상 DOM과 새 가상 DOM을 비교 한 뒤 변경사항을 식별한 후, 변경이 필요한 부분만 실제 DOM에 적용합니다.일단 배치 이후 렌더링 주기는 일정하게 정해져있는게 아닌 리액트가 판단하는 최적의 상황에만 적용이되는데, 이 주기는 애플리케이션의 현재 상태, 브라우저의 작업 부하, 그리고 React의 내부 로직에 따라 유동적으로 적용 된다.
// 리액트 17
function handleClick() {
sleep(3000).then(() => { // 외부 이벤트로 인해 상태가 바뀌었으니 두 번 리렌더링됨!
setCounter((c) => c + 1)
setFlag((f) => f + 1)
})
}
리액트 17 이하 버전에 존재 했던 배치
기능은 리액트 이벤트 핸들러 내부에서만 자동적으로 배치가 적용되고 있었다. (예시 : onClick
, onChange
등)
하지만 리액트 18에 등장한 자동 배치(Automatic Batching)는 React 18에서 이전 동작과 다르게, Promise
, setTimeout
등 외부 이벤트에서도 모두 배치 작업이 자동으로 적용된다.
루트 컴포넌트를 브라우저의 DOM 노드 안에 React 컴포넌트를 표시하는 루트를 생성하는 API인 createRoot
를 사용해서 만들면 자동으로 적용된다!
import ReactDom from 'react-dom/client'
const rootElement = document.getElementById('root')
const root = ReactDOM.createRoot(rootElement)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
만약 CRA로 만든 최신 버전의 리액트 프로젝트라면 자동으로 적용이 되어있다.
참고로 서버 사이드 렌더링을 사용하는 프로젝트라면 createRoot
가 아닌 hydreateRoot
를 사용해야 한다.
flushSync
를 이용하자자동 배치로 인해 원하는 동작을 하지 않는다면 ? flushSync
를 이용하자
import { flushSync } from `react-dom`
function handleClick() {
flushSync(() => {
setCounter((c) => c + 1)
})
flushSync(() => {
setFlag((f) => f + 1)
})
}