React에서 상태들을 한번에 묶어서 렌더링을 시키는 기능 - 자동 배치

JangGwon·2024년 4월 21일
1

들어가며

약 한 달전 데브코스 멘토님으로부터 리액트는 상태 변화같은게 실시간으로 적용되고 작동할까요? 라는 질문을 받았는데 그때 저는 대답을 하지 못했던 기억이 있었습니다. 물론 아닌것을 알고 있었지만, 왜? 어떤 기능 때문인지, 어떤 상황에서 하는지 잘 알지 못했기 때문에 혹시나 추가 질문이 들어올까... 무서워서 대답을 피했던적이 있습니다.

그러고 잠시 잊고 있다가 오늘 모던 리액트 딥다이브책에서 해당 질문의 답이 담긴 이 10장 - 리액트 18에 추가된 기능 챕터의 자동 배치 부분을 보게 되었고 물론 지나간 일이지만, 만약 타임머신이 개발 된다면 그 질문 받았을 당시로 돌아가 당당히 대답할 수 있도록 오늘 책에서 공부했던 자동배치 부분과 책 내용 외에도 추가적으로 찾은 내용들과 함께 머릿속에도 그리고 포스팅으로도 정리를 진행했습니다



일단 배치(Batching)란?

리액트가 여러 상태 업데이트를 하나의 리렌더링으로 묶는 것을 의미한다.
예를 들면, 한 번의 이벤트 발생으로 두 개 이상의 상태를 수정, 업데이트 해야한다고 했을 때, 이것을 하나의 리렌더링으로 묶어서 수행하게 기능입니다.

왜 이게 성능 향상에 도움이 될까?

// 배치를 사용하지 않는다면?
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 함수 호출하고 리렌더링!
 	}
// 이렇게 여러 상태값이 바뀌어도 한번만 렌더링됨

배치는 어떻게 작동되는걸까 ?

  1. 일단 React에서는 상태 변경 요청이 발생할 때마다, React는 이 변경 사항들을 가상 DOM에 적용해놓는다.
  2. 이후 최적의 상황이 온다면? 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)
    })
}

0개의 댓글

관련 채용 정보