flushSync

김동현·2026년 3월 17일

flushSync

⚠️ 함정(Pitfall)

flushSync를 사용하는 것은 흔하지 않으며 앱의 성능을 해칠 수 있어요.

소개

flushSync를 사용하면 제공된 콜백 내부의 모든 업데이트를 React가 동기적으로 플러시(flush)하도록 강제할 수 있어요. 이렇게 하면 DOM이 즉시 업데이트되는 것을 보장할 수 있어요.

flushSync(callback)

레퍼런스

flushSync(callback)

flushSync를 호출하면 React가 대기 중인 모든 작업을 플러시하고 DOM을 동기적으로 업데이트하도록 강제해요.

import { flushSync } from 'react-dom';

flushSync(() => {
  setSomething(123);
});

대부분의 경우, flushSync는 피할 수 있어요. flushSync는 최후의 수단으로 사용하세요.

아래에서 더 많은 예제를 확인하세요.

매개변수(Parameters)

  • callback: 함수예요. React는 이 콜백을 즉시 호출하고 그 안에 포함된 모든 업데이트를 동기적으로 플러시해요. 대기 중인 업데이트나 Effects, 또는 Effects 내부의 업데이트도 함께 플러시할 수 있어요. 만약 이 flushSync 호출의 결과로 업데이트가 일시 중단(suspend)되면, 폴백이 다시 표시될 수 있어요.

반환값(Returns)

flushSyncundefined를 반환해요.

주의사항(Caveats)

  • flushSync는 성능을 크게 해칠 수 있어요. 신중하게 사용하세요.
  • flushSync는 대기 중인 Suspense 바운더리가 fallback 상태를 보여주도록 강제할 수 있어요.
  • flushSync는 대기 중인 Effects를 실행하고 반환하기 전에 그 안에 포함된 모든 업데이트를 동기적으로 적용할 수 있어요.
  • flushSync는 콜백 내부의 업데이트를 플러시하기 위해 필요한 경우 콜백 외부의 업데이트도 플러시할 수 있어요. 예를 들어, 클릭으로 인한 대기 중인 업데이트가 있다면, React는 콜백 내부의 업데이트를 플러시하기 전에 그것들을 먼저 플러시할 수 있어요.

💡 부연 설명: "플러시(flush)"라는 용어가 생소할 수 있는데, 이건 "대기 중인 작업을 즉시 처리해서 완료시킨다"는 의미예요. React는 보통 성능을 위해 여러 업데이트를 모아서 한 번에 처리하는데(batching), flushSync를 사용하면 이런 최적화를 무시하고 즉시 처리하도록 강제하는 거죠.


사용법

서드파티 통합을 위한 업데이트 플러시

브라우저 API나 UI 라이브러리 같은 서드파티 코드와 통합할 때, React가 업데이트를 플러시하도록 강제하는 것이 필요할 수 있어요. flushSync를 사용해서 콜백 내부의 상태 업데이트를 동기적으로 플러시하도록 React에게 강제하세요:

flushSync(() => {
  setSomething(123);
});
// 이 줄에 도달했을 때, DOM은 이미 업데이트되어 있어요.

이렇게 하면 다음 코드 줄이 실행될 때쯤에는 React가 이미 DOM을 업데이트했다는 것을 보장할 수 있어요.

flushSync를 사용하는 것은 흔하지 않으며, 자주 사용하면 앱의 성능을 크게 해칠 수 있어요. 앱이 React API만 사용하고 서드파티 라이브러리와 통합하지 않는다면, flushSync는 필요 없을 거예요.

하지만, 브라우저 API 같은 서드파티 코드와 통합할 때는 도움이 될 수 있어요.

일부 브라우저 API는 콜백 내부의 결과가 콜백이 끝날 때까지 DOM에 동기적으로 작성되기를 기대해서, 브라우저가 렌더링된 DOM으로 뭔가를 할 수 있도록 해요. 대부분의 경우 React가 이걸 자동으로 처리해주지만, 어떤 경우에는 동기 업데이트를 강제하는 것이 필요할 수 있어요.

예를 들어, 브라우저의 onbeforeprint API를 사용하면 인쇄 대화상자가 열리기 직전에 페이지를 즉시 변경할 수 있어요. 이건 문서가 인쇄에 더 잘 표시되도록 커스텀 인쇄 스타일을 적용하는 데 유용해요. 아래 예제에서는 onbeforeprint 콜백 내부에서 flushSync를 사용해서 React 상태를 DOM에 즉시 "플러시"해요. 그러면 인쇄 대화상자가 열릴 때쯤에는 isPrinting이 "yes"를 표시해요:

// src/App.js
import { useState, useEffect } from 'react';
import { flushSync } from 'react-dom';

export default function PrintApp() {
  const [isPrinting, setIsPrinting] = useState(false);

  useEffect(() => {
    function handleBeforePrint() {
      flushSync(() => {
        setIsPrinting(true);
      })
    }

    function handleAfterPrint() {
      setIsPrinting(false);
    }

    window.addEventListener('beforeprint', handleBeforePrint);
    window.addEventListener('afterprint', handleAfterPrint);
    return () => {
      window.removeEventListener('beforeprint', handleBeforePrint);
      window.removeEventListener('afterprint', handleAfterPrint);
    }
  }, []);

  return (
    <>
      <h1>isPrinting: {isPrinting ? 'yes' : 'no'}</h1>
      <button onClick={() => window.print()}>
        Print
      </button>
    </>
  );
}

flushSync 없이는 인쇄 대화상자가 isPrinting을 "no"로 표시할 거예요. React가 비동기적으로 업데이트를 일괄 처리하기 때문에, 상태가 업데이트되기 전에 인쇄 대화상자가 표시되는 거죠.

💡 부연 설명: 이 예제는 flushSync가 실제로 필요한 경우를 잘 보여줘요. 브라우저의 인쇄 API는 beforeprint 이벤트가 끝나는 순간의 DOM 상태를 가지고 인쇄 미리보기를 만들어요. 만약 flushSync 없이 setIsPrinting(true)만 호출하면, React가 나중에 상태를 업데이트하기 때문에 인쇄 대화상자에는 업데이트 전의 "no"가 보이게 되는 거예요.

⚠️ 함정(Pitfall)

flushSync는 성능을 크게 해칠 수 있고, 예상치 못하게 대기 중인 Suspense 바운더리가 폴백 상태를 보여주도록 강제할 수 있어요.

대부분의 경우 flushSync는 피할 수 있으니, flushSync는 최후의 수단으로만 사용하세요.


문제 해결(Troubleshooting)

"flushSync was called from inside a lifecycle method" 에러가 발생해요

React는 렌더링 중간에 flushSync를 할 수 없어요. 그렇게 하면 아무 동작도 하지 않고(noop) 경고를 표시해요:

Warning: flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.

이건 다음과 같은 곳에서 flushSync를 호출하는 경우를 포함해요:

  • 컴포넌트를 렌더링하는 중
  • useLayoutEffect 또는 useEffect Hook 안
  • 클래스 컴포넌트 라이프사이클 메서드 안

예를 들어, Effect 안에서 flushSync를 호출하면 아무 동작도 하지 않고 경고가 표시돼요:

import { useEffect } from 'react';
import { flushSync } from 'react-dom';

function MyComponent() {
  useEffect(() => {
    // 🚩 잘못됨: Effect 안에서 flushSync 호출
    flushSync(() => {
      setSomething(newValue);
    });
  }, []);

  return <div>{/* ... */}</div>;
}

이를 고치려면, 보통 flushSync 호출을 이벤트로 옮기고 싶을 거예요:

function handleClick() {
  // ✅ 올바름: 이벤트 핸들러에서 flushSync는 안전해요
  flushSync(() => {
    setSomething(newValue);
  });
}

이벤트로 옮기기 어렵다면, 마이크로태스크에서 flushSync를 연기할 수 있어요:

useEffect(() => {
  // ✅ 올바름: flushSync를 마이크로태스크로 연기
  queueMicrotask(() => {
    flushSync(() => {
      setSomething(newValue);
    });
  });
}, []);

이렇게 하면 현재 렌더링이 완료되도록 허용하고, 업데이트를 플러시하기 위해 다른 동기 렌더링을 예약해요.

💡 부연 설명: queueMicrotask는 현재 실행 중인 JavaScript 코드가 완료된 직후에 실행될 마이크로태스크를 대기열에 추가하는 브라우저 API예요. 이렇게 하면 현재 렌더링 사이클을 방해하지 않으면서도 매우 빠르게 flushSync를 실행할 수 있어요.

⚠️ 함정(Pitfall)

flushSync는 성능을 크게 해칠 수 있는데, 이 특정 패턴은 성능 면에서 훨씬 더 나빠요. 마이크로태스크에서 flushSync를 탈출구로 호출하기 전에 다른 모든 옵션을 다 시도해보세요.

💡 부연 설명: 마이크로태스크에서 flushSync를 호출하는 건 "정말 어쩔 수 없을 때만" 사용하는 마지막 탈출구예요. 이렇게 하면 성능이 두 번 타격을 받게 되는데:
1. flushSync 자체가 동기 렌더링을 강제해서 성능에 안 좋고
2. 마이크로태스크를 사용하면 추가적인 렌더링 사이클이 생기기 때문이에요.

가능하면 항상 이벤트 핸들러 안에서 flushSync를 사용하는 것이 최선이에요!

profile
프론트에_가까운_풀스택_개발자

0개의 댓글