⚠️ 함정(Pitfall)
flushSync를 사용하는 것은 흔하지 않으며 앱의 성능을 해칠 수 있어요.
flushSync를 사용하면 제공된 콜백 내부의 모든 업데이트를 React가 동기적으로 플러시(flush)하도록 강제할 수 있어요. 이렇게 하면 DOM이 즉시 업데이트되는 것을 보장할 수 있어요.
flushSync(callback)
flushSync(callback)flushSync를 호출하면 React가 대기 중인 모든 작업을 플러시하고 DOM을 동기적으로 업데이트하도록 강제해요.
import { flushSync } from 'react-dom';
flushSync(() => {
setSomething(123);
});
대부분의 경우, flushSync는 피할 수 있어요. flushSync는 최후의 수단으로 사용하세요.
callback: 함수예요. React는 이 콜백을 즉시 호출하고 그 안에 포함된 모든 업데이트를 동기적으로 플러시해요. 대기 중인 업데이트나 Effects, 또는 Effects 내부의 업데이트도 함께 플러시할 수 있어요. 만약 이 flushSync 호출의 결과로 업데이트가 일시 중단(suspend)되면, 폴백이 다시 표시될 수 있어요.flushSync는 undefined를 반환해요.
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는 최후의 수단으로만 사용하세요.
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를 사용하는 것이 최선이에요!