[T]setTimeout(callback, 0)은 무엇일까?

eeensu·2025년 4월 2일

React 실무

목록 보기
24/25

브라우저에서 실행되는 js는 싱글스레드 언어이기 때문에 "한번에 하나의 작업"만 처리할 수 있다. 하지만 이런 환경속에서도, 실행 순서가 특정 타이밍에 적용되도록 보장해야할 순간이 생길 수 있다. 예를 들어, DOM 조작이 완료된 이후에 실행되어야할 코드이거나, react에서 상태 업데이트 이후 DOM에 반영된 후 처리되어여야할 작업들이다.


react에서의 상황

react의 상태 업데이트는 비동기적으로 처리된다. setState 코드를 만났다고 해서 그 즉시 DOM이 변경되는 것은 아니다. 이는 React 상태 업데이트의 기본 작동 방식이니 잘 알고 있을 테고...

하지만 만약 상태가 DOM에 반영된 이후에 실행되어야할 코드가 있다면? 실행 순서를 보장해야 할 필요가 있다. 그 방법은, 권장하진 않지만 아래와 같은 잡기술이 있다.

const CountTest: FC = () => {
  const [count, setCount] = useState<number>(0)

  const handleClick = () => {
    setCount(count + 1);

    // 상태 업데이트 이후 DOM 변경이 완료된 시점에 작업 실행
    setTimeout(() => {
      console.log(count);
    }, 0);
  };

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </>
  );
}

이렇게 하면, 변경된 state가 DOM에 반영된 이후에 console.log() 가 실행되도록 할 수 있다.


어떻게 동작이 이루어지는 것일까?

  • 실행되다가 setTimeout 코드를 만나게 되면, 여기에 넣어진 콜백함수는 0초라고 해도, 즉시 실행되는 것이 아니라 이벤트 루프의 태스크 큐(Task Queue)에 넣어진다.
  • 현재 실행 중인 코드(콜 스택에 있는 코드)가 모두 실행된 후에 큐에 있는 콜백함수들이 콜스택으로 넘어와 그제서야 실행이 되는 구조이다.
  • 한마디로, setCount(count + 1) 가 이루어진 이후에야 setTimeout의 콜백함수가 실행된다는 뜻이다.

단, 실제로 0초 뒤에 실행되는것은 아니다!

  • 0초 뒤에 실행된다고 하면, 순서 보장없이 바로 실행되는 것 같이 오해할 수 있다.
  • 하지만 실제로는 그렇지 않다. 쉽게 말하면 코드를 실행하는 구역자체를 변경하는 것이기에, 실질적으로는 순서가 보장된다.
  • 또한, 브라우저는 현재 실행 중인 코드(콜 스택에 있는 코드)가 모두 완료되었을 때 태스크 큐의 작업을 실행하기 때문에, 최소 지연시간이 발생할 수 있다. (보통 4ms)



실행 순서를 보장하는 다른 방법은 없을까?

사실 개인적인 생각으론, 위의 방법은 현업에서 사용을 지양해야한다고 생각한다. 그 코드를 작성한 본인은 금방 이해할 수 있지만, 같이 리뷰하며 보는 동료 개발자는 음.. 이게 무슨 의도지? 하며 생각할 수 있을 것 같기 때문이다. 사실 위의 방법은 실무에 직접적으로 사용하기 보단 이벤트 루프의 작동 방식 이해에 좋은 예시 코드라 생각한다. 현업에선 위 방법을 대신하여 아래의 3개 방법을 추천한다.

  1. useEffect
    가장 정석적인 방법이라 할 수 있다. DOM이 업데이트 된 직후 특정 작업을 실행하도록 할 수 있다.
useEffect(() => {
  console.log(`The updated count in DOM is ${count}`);
}, [count]); // count가 변경된 후 실행됨
  1. flushSync
    react에서 제공하는 flushSync를 사용하면 상태 업데이트를 동기적으로 강제 실행할 수 있다. 하지만 이는 react의 기본적인 구조와 특징에 위배되는 기능이므로, react에서도 공식적으로 사용을 지양해달라곤 하고 있다.
import { flushSync } from 'react-dom';

const handleClick = () => {
  flushSync(() => {
    setCount(count + 1);
  });
  console.log(`The updated count in DOM is ${count + 1}`);
};
profile
안녕하세요! 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글