드래그를 할 때 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가 반영 안 됨
};
- updatePosition 내부에서 setWorkBlocks 를 호출한다.
- setWorkBlocks는 비동기이므로, 다음 렌더링 사이클에서 반영된다.
- updateWorkTime은 이전 상태(workBlocks)를 사용하여 시간이 업데이트 되지 않음.
- DevTools가 켜져 있으면 브라우저 렌더링/이벤트 루프 속도 자체가 느려진다.
- 결과적으로 requestAnimationFrame이 더 늦게 실행되며, 그 전에 React의 setWorkBlocks가 의도치 않게 더 일찍 반영될 가능성이 있다.
상태는 여전히 비동기지만, DevTools로 브라우저가 느려진 탓에 우연히 setState → 상태 반영 → updateWorkTime 실행 순서가 맞아떨어진 것!
📌 React의 setState 작동 원리
setWorkBlocks(newState);
console.log(workBlocks); // ❌ 여기서의 workBlocks는 이전 값
- React는 성능 최적화를 위해 state 변경 요청을 즉시 반영하지 않고, 일괄 처리(batch update) 한다.
- React는 변경 요청들을 수집한 뒤, 렌더링 사이클에서 한 번에 처리한다.
- 따라서 setState 직후에는 아직 새로운 상태가 적용되지 않은 상태이다.
사용자 이벤트 발생 (예: onMouseMove)
↓
setState (예: setWorkBlocks) 호출
↓
React는 이 상태를 큐에 넣음 (즉시 반영 ❌)
↓
브라우저의 다음 렌더링 타이밍에 새로운 상태 반영
↓
컴포넌트 리렌더링됨 (이 시점에만 새로운 상태 사용 가능)
드래그 애니메이션을 구현하기 위해, requestFrameAnimation 콜백 함수 사용하기 때문이었다.
Window: requestAnimationFrame() method - Web API | MDN
Window: requestAnimationFrame() method - Web API | MDN
콜백 목록의 항목을 고유하게 식별하는 요청 id인 long 정수 값. 이것은 0이 아닌 값이지만, 그 값에 대해 어떠한 다른 추측을 할 수 없습니다. 이 값을 window.cancelAnimationFrame() 에 전달해 콜백 요청
developer.mozilla.org
requestFrameAnimation 에 대한 이모저모
- DevTools을 켜면 이벤트 루프가 느려져 requestAnimationFrame의 실행 시간이 오래 걸린다
- requestAnimationFrame와 같이 react context 외부에서 실행되는 비동기 콜백은 리액트의 batch update를 보장할 수 없다.
- import {unstable_batchedUpdates} from 'react-dom'; 등을 이용해 배치 업데이트를 강제할 수 있다.
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);
좌: 개발자 도구를 키지 않고 새로고침 후 개발자 도구 확인
우: 개발자 도구를 켜고 새로고침 후 확인

- 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 |
|---|