[BFE.dev] useEffect() timing 3

치만·2026년 1월 31일

BFE.dev react

목록 보기
26/28
post-thumbnail

useEffect() timing 3

'infiniteLoopProtection:false'
import * as React from "react";
import { useState, useEffect } from "react";
import { createRoot } from "react-dom/client";
import { screen, fireEvent } from "@testing-library/dom";

function App() {
  const [state, setState] = useState(0)
  console.log(1)
  
  const start = Date.now()
  while (Date.now() - start < 50) {
    window.timestamp = Date.now()
  }

  useEffect(() => {
    console.log(2)
  }, [state])

  Promise.resolve().then(() => console.log(3))

  setTimeout(() => console.log(4), 0)

  const onClick = () => {
    console.log(5)
    setState(num => num + 1)
    console.log(6)
  }
  return <div>
    <button onClick={onClick}>click me</button>
  </div>
}

const root = createRoot(document.getElementById('root'));
root.render(<App/>)

setTimeout(() => fireEvent.click(screen.getByText('click me')), 100)

출력 결과

1
3
4
2
5
6
1
2
3
4

풀이 과정

1. 초기 렌더링

  • 1 출력: root.render(<App />)가 실행되면서 1이 출력됩니다.

  • 3 출력: microtask Queue인 Promise의 then. 콜백 함수를 실행해 3이 출력됩니다.

  • 4 출력: task Queue에 있는 setTimeout 콜백을 실행하여 4가 출력됩니다.

  • 2 출력: useEffect가 실행되어 2가 출력됩니다.

  • 5 출력: 클릭 이벤트가 실행되어 5가 출력됩니다.

  • 6 출력: 다음으로 6이 출력됩니다.


2. 상태 업데이트 후 재렌더링

  • 1 출력: <App />이 리렌더되고 1이 출력됩니다.

  • 2 출력: useEffect가 실행되어 2가 출력됩니다.

🤔 왜 리렌더되고 microtask보다 useEffect가 먼저 실행될까?

Effect가 클릭과 같은 사용자 인터랙션에 의해 발생한 경우, React는 브라우저가 업데이트된 화면을 그리기 전에 Effect를 실행할 수 있습니다.

이는 Effect의 결과가 이벤트 시스템에서 즉시 관찰될 수 있도록 보장하기 위함이며, 대부분의 경우 이 동작은 문제없이 기대한 대로 동작합니다.


페인트 이전에 동기적으로 실행되는 경우

  • 사용자 인터랙션으로 인해 리렌더가 발생한 경우
  • layout effect 하에서 스케줄링된 경우
  • React 내부적으로 여유 시간이 있다고 판단한 경우
  • 3 출력: microtask Queue인 Promise의 then. 콜백 함수를 실행해 3이 출력됩니다.

  • 4 출력: task Queue에 있는 setTimeout 콜백을 실행하여 4가 출력됩니다.

profile
🌱개발 기록장

0개의 댓글