본문 바로가기
개발 공부/React

개발자도구를 켰을 때만 실행되는 코드가 있다…? (batch update, 이벤트루프)

by 진!!!!! 2025. 10. 25.

📌 상황

드래그를 할 때 updatePosition()으로 컴포넌트의 위치를 업데이트하고, updateWorkTime()으로 해당 데이터의 시간을 업데이트를 하여 드래그 시 컴포넌트 내의 시간이 함께 변경되는 코드를 작성했다.

그런데 개발자도구를 켜면 코드가 실행되고, 개발자도구를 끄면 코드가 실행되지 않았다.

 

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 


 

📌 분석

나의 가설

  • updatePosition()과 updateWorkTime() 두 함수 모두 내부에서 useState로 선언한 workBlock을 기반으로 데이터를 가공한 후, setWorkBlock()을 실행한다.
  • setState가 비동기로 실행되어 updatePosition()이 실행되기 전 updateWorkTIme이 실행되어 updateWorkTime이 실행되지 않는 것 처럼 보인다.
onMouseMove = () => {
  updatePosition();       // position을 update한 후 setWorkBlocks 실행
  updateWorkTime();       // position을 기반으로 workTime을 update한 후 setWorkBlocks 실행.
  						  //그러나 아직 업데이트된 workBlocks가 반영되지 않음 
};
 
  • 그러나 개발자 도구를 켜면 브라우저가 느려져 workBlock이 업데이트된 후 updateWorkTime이 실행된다.
  • setState는 batch 업데이트를 하기 때문에, 대부분의 경우는 브라우저 속도에 크게 영향을 받지 않지만, 애니메이션 구현을 위해 사용 중인 requestFrameAnimation에서 딜레이가 크게 발생하여 batch update에도 영향을 미치는 것으로 예상되었다.

📌 테스트

테스트 방법:

updateWorkTime 함수 내에서 console.log로 디버깅

좌: 개발자도구를 끄고 실행 우: 개발자도구를 켜고 실행

  • updateWorkTime 함수는 제대로 실행되지만, workBlock의 workTime은 제대로 업데이트되지 않는 것을 확인할 수 있었다.

📌 동작과정

리액트의 상태 업데이트 흐름

onMouseMove = () => {
  updatePosition();       // 여기서 setWorkBlocks(updatedItems)
  updateWorkTime();       // 여기선 아직 업데이트된 workBlocks가 반영 안 됨
};
 
  1. updatePosition 내부에서 setWorkBlocks 를 호출한다.
  2. setWorkBlocks는 비동기이므로, 다음 렌더링 사이클에서 반영된다.
  3. updateWorkTime은 이전 상태(workBlocks)를 사용하여 시간이 업데이트 되지 않음.

 

그런데 DevTools를 켜면?

  • DevTools가 켜져 있으면 브라우저 렌더링/이벤트 루프 속도 자체가 느려진다.
  • 결과적으로 requestAnimationFrame이 더 늦게 실행되며, 그 전에 React의 setWorkBlocks가 의도치 않게 더 일찍 반영될 가능성이 있다.

상태는 여전히 비동기지만, DevTools로 브라우저가 느려진 탓에 우연히 setState  상태 반영  updateWorkTime 실행 순서가 맞아떨어진 것!


📌 React의 setState 작동 원리

setState는 비동기적(non-blocking) 으로 작동한다.

setWorkBlocks(newState);
console.log(workBlocks); // ❌ 여기서의 workBlocks는 이전 값
 
  • React는 성능 최적화를 위해 state 변경 요청을 즉시 반영하지 않고, 일괄 처리(batch update) 한다.
  • React는 변경 요청들을 수집한 뒤, 렌더링 사이클에서 한 번에 처리한다.
  • 따라서 setState 직후에는 아직 새로운 상태가 적용되지 않은 상태이다.

사용자 이벤트 발생 (예: onMouseMove)
↓
setState (예: setWorkBlocks) 호출
↓
React는 이 상태를 큐에 넣음 (즉시 반영 ❌)
↓
브라우저의 다음 렌더링 타이밍에 새로운 상태 반영
↓
컴포넌트 리렌더링됨 (이 시점에만 새로운 상태 사용 가능)
 


📌 DevTools를 켰을 때 코드가 동작하는 이유

드래그 애니메이션을 구현하기 위해, requestFrameAnimation 콜백 함수 사용하기 때문이었다.

requestFrameAnimation이란?

Window: requestAnimationFrame() method - Web API | MDN

 

Window: requestAnimationFrame() method - Web API | MDN

콜백 목록의 항목을 고유하게 식별하는 요청 id인 long 정수 값. 이것은 0이 아닌 값이지만, 그 값에 대해 어떠한 다른 추측을 할 수 없습니다. 이 값을 window.cancelAnimationFrame() 에 전달해 콜백 요청

developer.mozilla.org

 

requestFrameAnimation 에 대한 이모저모

  1. DevTools을 켜면 이벤트 루프가 느려져 requestAnimationFrame의 실행 시간이 오래 걸린다
  2. requestAnimationFrame와 같이 react context 외부에서 실행되는 비동기 콜백은 리액트의 batch update를 보장할 수 없다.
    1. import {unstable_batchedUpdates} from 'react-dom'; 등을 이용해 배치 업데이트를 강제할 수 있다.

 

검증: 정말 DevTools를 열고 requestAnimationFrame를 실행하면 시간이 오래 걸릴까?

let last = performance.now();
  let count = 0;
  const maxCount = 10;

  function loop() {
    if (count >= maxCount) return;

    const now = performance.now();
    console.log(`Frame ${count + 1}: ${Math.round(now - last)}ms`);
    last = now;
    count++;

    requestAnimationFrame(loop);
  }

  requestAnimationFrame(loop);
 

좌: 개발자 도구를 키지 않고 새로고침 후 개발자 도구 확인

우: 개발자 도구를 켜고 새로고침 후 확인 

첫번째 프레임을 실행했을 때의 시간이 18ms에서 37ms로 느려졌다. 그 이후는 큰 영향 x

 

DevTools를 켜면 이벤트 루프가 느려져서 requestAnimationFrame도 느려진다.

📌 해결

  • updatePosition과 updateWorkTime을 분리하지 않고, 한번에 실행한다.
  • 한번의 렌더링에 setState를 한번만 수정하도록 로직을 수정하여 해결했다.
onMouseMove = () => {
  updatePositionAndTime(); //함수 내부에서 x좌표를 기반으로 time을 업데이트
};
 

 

(현재는 onMouseMove가 아닌 window.addEventListener에 PointerMove 이벤트를 등록하여 상태를 업데이트하는 방식으로 변경하였다.)

 

 

관련 PR 같이 보기: https://github.com/softeerbootcamp-6th/Team1-ImSnacks/pull/130

'개발 공부 > React' 카테고리의 다른 글

소스코드로 react hook 동작 원리 살펴보기  (0) 2025.06.15