Front-End/React

자동 배치(Automatic Batching)

Voyage_dev 2024. 7. 31. 14:59
✔️ React 18에서 추가된 자동 배치(Automatic Batching)에 대해 살펴보자

 

리액트에서 항상 관심을 가지는 있는 주제는 바로 상태를 어떻게 업데이트 할 것인가 이다.

 

useTransition이나 useDeferredValue 같은 훅들도 어떻게 하면 상태를 잘 업데이트할 수 있을까에 대한 고민에서 나온 기능이라고 볼 수 있다.

 

리액트 컴포넌트에서는 하나의 상태만 가지고 있기 보다는 두 개 이상의 상태를 가지고 있는 경우가 훨씬 더 많다. 이러한 경우 상태가 하나씩 업데이트가 될 때마다 컴포넌트는 리렌더링을 수행하게 된다. React 트리가 깊고 복잡해질수록 다루는 상태가 많아지게 되고 리렌더링 횟수가 기하급수적으로 늘어나게 되는 문제가 발생한다.

 

이러한 문제를 해결하기 위해서 나온 기능이 바로 자동 배치 기능이다. 리액트 18 버전에서는 하나의 컴포넌트 안에서 여러 개의 상태를 하나의 그룹으로 묶어서 업데이트를 해주는 자동 배치 기능이 추가 되었다.

 

자동 배치란?

 

How to Upgrade to React 18 – React

The library for web and native user interfaces

react.dev

 

배치 업테이트의 단위

  • 리액트 18 이전 : 이벤트 핸들러
  • 리액트 18 : 프로미스, setTimeout, 이벤트 핸들러, 그 외 이벤트 등
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will render twice, once for each state update (no batching)
}, 1000);
  • 이전에는 이런 어떤 함수에서 상태를 두 개를 업데이트 한다고 했을 때 렌더링이 두 번씩 일어났다면
// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);
  • Autimatic Batrching으로 인해서 두 개의 상태를 업데이트 하더라도 이 setTimeout안에 두 개의 상태가 업데이트가 될 때 리렌더링은 오직 한 번만 일어난다.

실제 예제 코드

import { useEffect, useState } from "react";

const sleep = (ms: number) => {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export default function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => console.log('rendered'));

  function handleClick() {
    sleep(3000).then(() => {
      setCount(c => c+1);
      setFlag(f => !f);
    })
  }

  return (
    <div>
      <button onClick={handleClick}>다음</button>
      <h1 style={{color: flag ? 'blue' : 'black'}}>{count}</h1>
    </div>
  )
}
  • 이전에는 상태 두 개가 업데이트 될 때마다 렌더링이 진행이 되기 때문에 useEffect(() => console.log('rendered')); 인해서 rendered가 두 번씩 찍히는데 18버전에서는 한 번만 찍힌다.

만약에 이 상태가 바뀔 때마다 리렌더링을 해주고 싶으면 flushSync 메서드를 쓰면 된다. 각각의 상태 바꿔주는 함수들마다 작성을 해 주면 상태가 바뀔 때 마다 리렌더링을 시켜줄 수 있다.

function handleClick() {
  sleep(3000).then(() => {
    flushSync(() => {
	    setCount(c => c+1);
	  });
    flushSync(() => {
	    setFlag(f => !f);
	  });
  })
}