티스토리 뷰

React

React의 flushSync

주섬이 2025. 9. 26. 20:06
반응형

React의 flushSync

React 18에 도입된 react-dom 패키지의 함수로, 상태 업데이트를 강제로 즉시 동기적으로 처리하도록 만드는 역할을한다.

 

리액트는 불필요한 리렌더링을 줄여 애플리케이션의 성능 최적화를 위해, 기본적으로 상태 업데이트를 비동기적으로 처리하고 여러 업데이트를 하나로 묶어(batching) 한번에 렌더링 하는 핵심적인 메커니즘으로 동작한다.

이때 자동 배치가 하나로 묶이는 기준은 아래와 같다.

  • 동일한 이벤트 핸들러 내부의 모든 상태 업데이트인 경우
    • 상태 업데이트가 아닌경우, 예를들어 API 호출이나 로컬 변수 변경, DOM 조작등은 배칭의 대상이 아니다. 이 경우에는 코드가 위치한 순서대로 실행된다.
  • 비동기 이벤트 핸들러 및 프로미스 내부 코드 블록 (React 18+)

만약, 한번에 반영되는 것이 아니라 즉시 DOM에 반영되어야하는 상황이 있을땐 flushSync를 사용하면된다.

 

flushSync

내부에 있는 상태 업데이트는 리액트의 비동기배치를 무시하고 해당 코드가 실행되는 시점에 즉시 리렌더링을 발생시켜 콜백이 완료되는 시점에 DOM이 최신 상태로 업데이트 된 것을 보장한다.

하지만 이 경우 리액트의 기본 최적화 방식을 우회하는 것이므로 과도하게 사용할 경우 성능 저하를 일으킬 수 있기에 특정 상황에서만 사용이 권장된다.

 

예시

1. 상태 변경 후 DOM이 즉시 업데이트 되어야할때

import { flushSync } from 'react-dom';

function MyComponent() {
  const [items, setItems] = useState([]);
  const listRef = useRef(null);

  const handleAddItems = () => {
    // flushSync를 사용하지 않으면, 상태 업데이트가 비동기적으로 처리되어
    // scrollToView가 호출되는 시점에 아직 새로운 아이템이 DOM에 추가되지 않을 수 있음
    flushSync(() => {
      setItems(prevItems => [...prevItems, '새로운 아이템']);
    });

    // flushSync 덕분에 scrollToView가 호출될 시점에는
    // 이미 새로운 아이템이 렌더링되어 DOM에 존재함을 보장
    if (listRef.current) {
      listRef.current.scrollTop = listRef.current.scrollHeight;
    }
  };

  return (
    <div>
      <ul ref={listRef}>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={handleAddItems}>아이템 추가</button>
    </div>
  );
}

 

2. 비동기 업데이트 시 DOM이 변경되었을때, 요소의 크기나 위치를 즉시 측정해야할때

import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

function MeasureHeightComponent() {
  const [showMore, setShowMore] = useState(false);
  const divRef = useRef(null);

  const handleToggle = () => {
    // flushSync를 사용하여 showMore 상태를 즉시 업데이트하고 DOM에 반영
    flushSync(() => {
      setShowMore(prev => !prev);
    });

    // 이제 DOM에 텍스트가 확실히 렌더링되었으므로, 올바른 높이를 측정할 수 있습니다.
    if (divRef.current) {
      console.log(`DIV의 현재 높이: ${divRef.current.offsetHeight}px`);
    }
  };

  return (
    <div>
      <h2>레이아웃 측정 예시</h2>
      <div ref={divRef} style={{ border: '1px solid black', padding: '10px' }}>
        <p>이것은 기본 텍스트입니다.</p>
        {showMore && (
          <p>
            추가 텍스트입니다. 이 텍스트는 보이기 전까지는 높이에 영향을 주지 않습니다.
          </p>
        )}
      </div>
      <button onClick={handleToggle}>
        {showMore ? '숨기기' : '더보기'}
      </button>
    </div>
  );
}

export default MeasureHeightComponent;

 

 

3. 즉각적인 시각적 피드백이 필요할때

무거운 작업을 시작하기전 로딩 스피너 보여주기 

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

function HeavyTaskComponent() {
  const [isLoading, setIsLoading] = useState(false);
  const [result, setResult] = useState(null);

  const startTask = () => {
    // 1. flushSync를 사용하여 로딩 상태를 즉시 DOM에 반영합니다.
    //    이 시점에 <p>Loading...</p>가 확실히 화면에 나타납니다.
    flushSync(() => {
      setIsLoading(true);
    });
    
    // 2. 이제 UI가 업데이트되었으므로, 무거운 작업을 시작합니다.
    //    사용자는 로딩 스피너를 보게 됩니다.
    let sum = 0;
    for (let i = 0; i < 2000000000; i++) {
      sum += i;
    }
    
    // 3. 작업이 완료되면 로딩 상태를 해제하고 결과를 업데이트합니다.
    setIsLoading(false);
    setResult(`작업 완료! 결과: ${sum}`);
  };

  return (
    <div>
      <h2>즉각적인 UI 반응 예시</h2>
      <button onClick={startTask} disabled={isLoading}>
        {isLoading ? '작업 중...' : '무거운 작업 시작'}
      </button>
      {isLoading && <p>Loading...</p>}
      {result && <p>{result}</p>}
    </div>
  );
}

export default HeavyTaskComponent;

 

반응형
최근에 올라온 글
최근에 달린 댓글
«   2025/10   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
반응형