Afaik
2025년Archive

9월 15일

오늘 배운 것 (TIL)

flushSync

  • React 18에서 도입된 함수로 React의 자동 배칭을 우회하여 상태 업데이트를 동기적으로 처리하도록 강제하는 기능

React의 기본 동작

  • 성능 최적화를 위해 여러 상태 업데이트를 하나의 리렌더링으로 묶어서 처리하는데 React 18 부터는 이 배칭이 더욱 강화되어 setTimeout, Promise, 네이티브 이벤트 핸들러에서도 자동으로 배칭이 일어난다.

flushSync의 동작 원리

  • flushSync는 이러한 배칭을 중단시키고, 함수 내부의 상태 업데이트를 즉시 동기적으로 처리한다.
import { flushSync } from "react-dom";

function handleClick() {
  flushSync(() => {
    setCount((c) => c + 1);
  });
  // 여기서 DOM이 이미 업데이트됨

  flushSync(() => {
    setFlag((f) => !f);
  });
  // 여기서 또 다른 DOM 업데이트 발생
}

주요 특징

  1. 동기적 실행: flushSync 내부의 업데이트는 함수가 끝나기 전에 즉시 DOM에 반영된다.
  2. Effect 실행: flushSync는 layout effects(useLayoutEffect)도 동기적으로 실행시키지만, 일반 effects(useEffect)는 여전히 비동기로 실행된다.
  3. 성능 영향: 배칭을 우회하므로 과도한 사용은 성능 저하를 일으킬 수 있다.

사용 사례

DOM 측정이 필요한 경우

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

  function addItem() {
    flushSync(() => {
      setItems((prev) => [...prev, newItem]);
    });
    // DOM이 즉시 업데이트되어 정확한 scrollHeight를 측정 가능
    listRef.current.scrollTop = listRef.current.scrollHeight;
  }
}

포커스 관리

function TodoList() {
  const [todos, setTodos] = useState([]);
  const inputRef = useRef(null);

  function addTodo() {
    flushSync(() => {
      setTodos((prev) => [...prev, newTodo]);
    });
    // 새로 추가된 input에 즉시 포커스
    inputRef.current?.focus();
  }
}

주의사항

  1. 성능: flushSync는 React의 최적화를 우회하므로 꼭 필요한 경우에만 사용해야 한다.
  2. Concurrent Features: flushSync 내부에서는 Suspense, 전환(transitions) 등의 concurrent 기능들이 제대로 작동하지 않을 수 있다.
  3. Effect 타이밍: useEffect는 여전히 비동기로 실행되므로, 동기적 실행이 필요하다면 useLayoutEffect를 사용해야 한다.