PromleeBlog
sitemap
aboutMe

posting thumbnail
React 18 자동 배칭과 동시성 - React, 알고 쓰자 3일차
React 18 Automatic Batching & Concurrency - React Explained Day 3

📅

🚀

들어가기 전에 🔗

안녕하세요, React 개발 파헤치기기 세 번째 날입니다. 어제 우리는 useState와 setState가 어떻게 상태를 관리하고 업데이트 요청을 처리하는지 살펴보았습니다. 특히, React가 여러 setState 호출을 묶어서 처리하는
배치(Batching)
기능에 대해 잠깐 언급했었죠.
오늘은 바로 이 '배치' 기능이 React 18에서 어떻게 더욱 강력해졌는지, 그리고 React가 더 부드럽고 반응성 좋은 사용자 인터페이스(UI)를 만들기 위해 도입한
동시성(Concurrency)
이라는 중요한 개념에 대해 깊이 알아볼 시간입니다.

🚀

자동 배칭 (Automatic Batching) 🔗

배칭(Batching)은 여러 상태 업데이트를 하나의 그룹으로 묶어서, 단 한 번의 리렌더링으로 처리하는 React의 최적화 기법입니다. 불필요한 렌더링을 줄여 성능을 향상시키는 아주 중요한 역할이죠.

React 17 이전: 제한적인 배칭 🔗

React 17 버전까지는 배칭이 주로 React 이벤트 핸들러 내에서만 자동으로 이루어졌습니다. 예를 들어 버튼 클릭 이벤트 핸들러 안에서 setState를 여러 번 호출하면, React는 이를 알아서 묶어 처리했습니다.
하지만 setTimeout, setInterval, Promise의 콜백 함수, 또는 네이티브 이벤트 리스너 내부에서 setState를 호출하면 각각의 업데이트가 별도의 리렌더링을 유발했습니다.
// React 17 이하 예시
function handleClick() {
  // 이벤트 핸들러 내: 자동으로 배칭됨 (리렌더링 1번)
  setCount(c => c + 1);
  setFlag(f => !f);
}
setTimeout(() => {
  // setTimeout 콜백 내: 배칭 안 됨 (리렌더링 2번 발생)
  setCount(c => c + 1); // 리렌더링 발생
  setFlag(f => !f);      // 또 리렌더링 발생
}, 1000);

React 18 이후: 어디서든 자동 배칭! 🔗

React 18부터는
자동 배칭(Automatic Batching)
기능이 도입되었습니다. 이제는 setTimeout, Promise, 네이티브 이벤트 핸들러 등 어디에서 setState를 호출하든 기본적으로 모든 업데이트를 자동으로 묶어서 처리합니다.
// React 18 예시
function handleClick() {
  // 이벤트 핸들러 내: 당연히 자동 배칭 (리렌더링 1번)
  setCount(c => c + 1);
  setFlag(f => !f);
}
setTimeout(() => {
  // setTimeout 콜백 내: 이제 자동으로 배칭 (리렌더링 1번)
  setCount(c => c + 1);
  setFlag(f => !f);
  // 모든 업데이트가 모아져서 단 한 번의 리렌더링만 발생
}, 1000);
이 변화 덕분에 개발자가 특별히 신경 쓰지 않아도 React가 알아서 불필요한 렌더링을 줄여주므로, 애플리케이션 성능이 전반적으로 향상되는 효과를 기대할 수 있습니다.
렌더링 방식 비교
렌더링 방식 비교
Q: "React 18의 자동 배칭이란 무엇이고 이전 버전과 어떻게 다른가요?"
A: "React 18부터는 이벤트 핸들러뿐만 아니라 setTimeout, Promise 등 비동기 작업 내에서도 여러 상태 업데이트를 자동으로 묶어 단일 리렌더링으로 처리하는 기능입니다. 이전 버전에서는 이벤트 핸들러 외부에서는 기본적으로 배칭이 적용되지 않았습니다."

자동 배칭과 Micro-task의 관계 🔗

자동 배칭이 어떻게 가능해졌을까요? React 18은 내부적으로 업데이트를 처리할 때 브라우저의
마이크로태스크(Micro-task)
큐를 활용합니다.
setState가 호출되면 즉시 렌더링을 예약하는 대신, 짧은 지연(주로 현재 실행 중인 코드 이후, 다음 마이크로태스크 처리 시점) 후에 렌더링을 수행하도록 일정을 잡습니다.
이 짧은 지연 시간 동안 발생하는 추가적인 setState 호출들은 자연스럽게 동일한 배치에 포함될 수 있게 됩니다.
➡️

마이크로태스크(Micro-task)란? 🔗

JavaScript 이벤트 루프에서 현재 실행 중인 스크립트가 완료된 직후, 다음 렌더링이나 다른 매크로태스크(Macro-task, 예: setTimeout 콜백)가 실행되기 전에 처리되는 작은 작업들의 대기열입니다. Promise의
then/catch/finally
콜백이 대표적인 마이크로태스크입니다.
이미지 제안:
간단한 이벤트 루프 다이어그램. Call Stack, Micro-task Queue, Macro-task Queue(Task Queue), Rendering 단계를 보여주고, setState 호출이 어떻게 Micro-task Queue를 거쳐 렌더링으로 이어지는지(React 18의 경우) 간략히 표현.

배칭을 깨야 할 때: flushSync 🔗

대부분의 경우 자동 배칭은 좋은 기능이지만, 아주 드물게 상태 업데이트 직후
즉시
DOM 변경사항을 확인하고 싶을 때가 있습니다. 예를 들어, 상태 업데이트로 인해 특정 입력 필드에 포커스를 옮겨야 하는 경우, React가 렌더링을 다음번으로 미루면 포커스 로직이 제대로 동작하지 않을 수 있습니다.
이럴 때 사용하는 것이 flushSync API입니다. flushSync로 감싸진 함수 내에서 호출된 setState는 자동 배칭에서 제외되고, 해당 업데이트를
동기적으로
즉시 처리하여 DOM을 업데이트합니다.
import { flushSync } from 'react-dom'; // react-dom에서 import
 
function handleClick() {
  flushSync(() => {
    setCount(c => c + 1); // 이 업데이트는 즉시 처리되어 DOM에 반영됨
  });
  // 여기서는 DOM 업데이트가 완료된 상태임
  // (예: 업데이트된 DOM 요소에 접근하거나 포커스를 설정할 수 있음)
  // setFlag(f => !f); // 이 업데이트는 flushSync 밖에 있으므로 다시 자동 배칭 규칙을 따름
}
flushSync는 성능에 영향을 줄 수 있으므로 꼭 필요한 경우에만 신중하게 사용해야 합니다. 대부분의 경우에는 자동 배칭의 이점을 누리는 것이 좋습니다.
flushSync사용 시 렌더링 차이
flushSync사용 시 렌더링 차이

🚀

동시성 (Concurrency) 🔗

동시성(Concurrency)은 React 18의 또 다른 핵심 개념이자, Fiber 아키텍처가 궁극적으로 지향하는 목표 중 하나입니다. 동시성은 여러 상태 업데이트 작업을
겹치거나 중단
하면서 처리할 수 있는 능력을 의미합니다.

동시성이 왜 중요할까요? 🔗

기존 React(Fiber 이전 또는 동시성 기능 비활성화 시)는 렌더링 작업을 시작하면 끝날 때까지 멈추지 않았습니다.
만약 렌더링 작업이 매우 크고 오래 걸린다면, 그동안 브라우저는 다른 중요한 작업(예: 사용자 입력 처리, 애니메이션)을 할 수 없어 화면이 멈추거나 버벅거리는 것처럼 보이게 됩니다.
동시성을 활용하면 React는 긴 렌더링 작업을 작은 단위로 나누어 처리하다가, 더 급한 업데이트(예: 사용자가 입력 필드에 글자를 입력하는 것)가 발생하면 진행 중이던 렌더링을 잠시 멈추고 급한 업데이트를 먼저 처리할 수 있습니다.
이후 다시 원래 하던 렌더링 작업을 이어서 진행합니다.
마치 여러 가지 일을 동시에 처리하는 멀티태스킹과 비슷하지만, 실제로는 작업을 잘게 쪼개고 우선순위에 따라 빠르게 전환하며 처리하는 방식에 가깝습니다.
이를 통해 복잡한 업데이트 중에도 UI의 반응성을 유지하여 사용자 경험을 크게 향상시킬 수 있습니다.

Transition API: 업데이트에 우선순위 부여하기 🔗

React 18은 동시성을 개발자가 직접 활용할 수 있는 새로운 API들을 제공합니다. 그중 대표적인 것이 startTransition입니다.
startTransition은 특정 상태 업데이트를
긴급하지 않은 것
(non-urgent)으로 표시하는 방법입니다. 이렇게 표시된 업데이트는 다른 긴급한 업데이트(예: 사용자 입력)에 의해 중단될 수 있으며, 렌더링이 진행되는 동안에도 UI는 계속 반응성을 유지합니다.
import React, { useState, useTransition } from 'react';
function SearchResults({ query }) {
  // ... 데이터 로딩 및 결과 표시 로직 ...
  return <div>{query} 검색 결과...</div>;
}
function App() {
  const [inputValue, setInputValue] = useState('');
  const [searchQuery, setSearchQuery] = useState('');
  const [isPending, startTransition] = useTransition(); // useTransition 훅 사용
 
  const handleChange = (e) => {
    // 1. 입력값 업데이트 (긴급)
    setInputValue(e.target.value);
    // 2. 검색 쿼리 업데이트 (긴급하지 않음 - Transition으로 감싸기)
    startTransition(() => {
      setSearchQuery(e.target.value);
    });
  };
 
  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} />
      {isPending ? " 로딩 중..." : <SearchResults query={searchQuery} />}
    </div>
  );
}
위 예시에서 사용자가 입력 필드에 글자를 입력하면, setInputValue는 즉시 실행되어 입력 필드가 바로바로 업데이트됩니다
(긴급)
.
반면, 검색 결과를 위한 setSearchQuery는 startTransition으로 감싸져 있어 우선순위가 낮습니다. 만약 검색 결과 렌더링이 오래 걸리더라도, 사용자의 입력(setInputValue) 처리를 방해하지 않습니다.
useTransition 훅은 startTransition 함수와 함께 isPending이라는 불리언 상태를 반환합니다. 이 값을 사용하여 전환(Transition) 작업이 진행 중일 때 로딩 상태를 표시하는 등 UI를 개선할 수 있습니다.
startTransition 사용 시 업데이트 처리 흐름도
startTransition 사용 시 업데이트 처리 흐름도
Q: "React의 동시성이란 무엇이며, startTransition은 어떤 역할을 하나요?"
A: "동시성은 React가 여러 상태 업데이트를 중단 가능하게 처리하여 UI 반응성을 유지하는 능력입니다. startTransition은 특정 업데이트를 긴급하지 않다고 표시하여, 이 업데이트가 긴급한 다른 업데이트를 막지 않도록 우선순위를 낮추는 역할을 합니다."

🚀

결론 🔗

오늘은 React 18의 핵심 기능인 자동 배칭과 동시성에 대해 알아보았습니다.
자동 배칭
: React 18부터는 이벤트 핸들러, setTimeout, Promise 등 위치에 상관없이 여러 setState 호출이 기본적으로 하나의 리렌더링으로 묶여 처리됩니다. 이는 불필요한 렌더링을 줄여 성능을 향상시킵니다. (React 17 이전에는 제한적)
flushSync
: 자동 배칭을 의도적으로 깨고 상태 업데이트를 동기적으로 즉시 처리해야 할 때 사용하는 API입니다. 신중하게 사용해야 합니다.
동시성
: React가 긴 렌더링 작업을 중단하고 더 긴급한 업데이트를 먼저 처리할 수 있게 하여 UI 반응성을 높이는 기능입니다. Fiber 아키텍처의 주요 목표 중 하나입니다.
startTransition
: 특정 상태 업데이트를 긴급하지 않은 '전환'으로 표시하여, 해당 업데이트가 UI 반응성을 저해하지 않도록 우선순위를 낮추는 API입니다. useTransition 훅을 통해 전환 상태(isPending)도 알 수 있습니다.
자동 배칭과 동시성은 React가 더 똑똑하고 사용자 친화적인 UI를 만들 수 있도록 돕는 강력한 도구입니다. 특히 동시성 기능은 앞으로 React가 데이터를 가져오거나
코드 스플리팅
을 처리하는 방식에도 큰 영향을 미칠 중요한 개념입니다.
다음 시간에는 React의 여러 훅들(useEffect, useLayoutEffect, useRef, useReducer 등)에 대해 더 깊이 탐구하고, 커스텀 훅을 통해 로직을 재사용하는 방법에 대해 알아보겠습니다.

참고 🔗