카운트다운 훅을 만들고 있는데, setInterval 안에서 찍는 count 값이 항상 0만 찍힙니다.
function useCountdown() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count); // 항상 0
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return count;
}
setCount(c => c + 1)로 업데이트는 정상 동작하는데, 콘솔에 찍는 count만 0 그대로예요. deps에 count를 넣으면 interval이 매초 재생성돼서 원래 의도가 아니고요.
deps 안 건드리고 최신값 참조하는 방법이 있나요?
댓글 3
전형적인 stale closure입니다. effect가 처음 실행될 때 캡처된
count=0가 interval 콜백 안에 박혀버리는 구조예요.패턴 ①: useRef로 최신값 미러링
deps를 비운 채 최신값만 읽고 싶을 때 쓰는 공식 패턴에 가깝습니다.
패턴 ②: Dan Abramov의 useInterval 훅
로직을 추상화한 커스텀 훅입니다. 콜백을 ref에 담아 항상 최신 버전을 호출합니다.
왜 setCount(c => c+1)은 됐고 console.log는 안 됐나
setCount의 updater 함수는 React가 호출 시점의 최신 state를 인자로 주입하기 때문입니다. 반면console.log(count)는 effect 클로저에 캡처된 값 그대로라 0이 박힌 거죠. 이 구분이 이 버그를 이해하는 핵심입니다.저도 이 버그 몇 번 겪고 나서는 "interval/subscription/eventListener 안에서 state 읽어야 하면 일단 ref 미러링" 규칙으로 갔어요. React 공식 문서 "Separating Events from Effects" 챕터에
useEffectEvent얘기도 있는데 아직 experimental이라 프로덕션에서는 useRef 쪽이 안전합니다.useRef 미러링 패턴으로 해결됐습니다. setCount가 왜 됐는지도 이해가 명확해졌어요. 감사합니다!