훅 안에서 내부 함수를 정의하고 그 안에서 state를 쓸 때는 클로저 문제가 발생한다.
useEffect(() => {
const handleVisibility = () => {
if (state !== "stopChecking") { // state가 바뀌어도 모름
console.log("visible");
}
}
document.addEventListener("visibilitychange", handleVisibility);
return () => document.removeEventListener("visibilitychange", handleVisibility);
}, []);
이런 경우 useEffect가 실행될 시점의 state 값을 캡쳐하고 그 값은 갱신되지 않는다.
클로저 문제라고 흔히 말하지만 원인 자체가 클로저는 아니다.
오히려 클로저는 외부 변수의 값을 복사해오는 게 아니라 참조를 하므로 외부 변수가 변경되면 내부에서도 반영이 된다.
let count = 0;
function makeLogger() {
return () => console.log(count);
}
const logger = makeLogger();
count = 10;
logger(); // 10
그 증거로 위의 바닐라 자바스크립트 코드는 최신 값을 잘 가져오는 것을 확인할 수 있다.
React의 경우는 함수형 컴포넌트가 리렌더링될 때마다 새로운 스코프를 만드는 것이 원인이다.
useEffect는 딱 한번만 실행되는 훅이라 handleVisibility는 리렌더링이 되어도 이전 스코프만 계속 참조한다.
따라서 외부 최신 값에 접근을 못 하니 클로저가 참조하는 외부 값이 옛날 값이라 생기는 문제이다.
해결법은 크게 3가지인데,
1. 내부에서 사용되는 state를 ref로 바꾼다 (가장 정석적)
2. 의존성 배열에 state를 넣는다
3. useCallback을 사용한다
1번 방법
const stateRef = useRef(state);
useEffect(() => {
stateRef.current = state;
}, [state]);
useEffect(() => {
const handleVisibility = () => {
if (stateRef.current !== "stopChecking") {
console.log("visible");
}
};
document.addEventListener("visibilitychange", handleVisibility);
return () => document.removeEventListener("visibilitychange", handleVisibility);
}, []);
2번 방법
useEffect 의존성 배열에 state 넣음. 코드는 생략함
3번 방법
const handleVisibility = useCallback(() => {
if (state !== "stopChecking") {
console.log("visible");
}
}, [state]);
useEffect(() => {
document.addEventListener("visibilitychange", handleVisibility);
return () => document.removeEventListener("visibilitychange", handleVisibility);
}, [handleVisibility]);
사실상 2번 방법과 동작은 같으며 두 방법 모두 state의 값이 변경될 때마다 핸들러를 지웠다가 재정의한다.
state가 자주 변하는 값이 아니라면 2, 3번 방법도 크게 문제될 것은 없다.
하지만 state가 자주 변하는 값이거나, 자신이 안정적인 방식을 선호한다면 ref를 하나 더 만드는 것이 좋다.
기초적인 내용이지만 가끔 헷갈려서 다시 정리함!
'IT > React & Next.js' 카테고리의 다른 글
| [TanStack Query] Query Key factories로 Query Key 관리하기 (0) | 2025.12.05 |
|---|---|
| [Infra] Biome.js VSCode 세팅 (1) | 2025.08.31 |
| [React/TypeScript] React-Spilt-Table 오픈소스 라이브러리 (1) | 2025.04.21 |
| [Next.js] RCC, RSC 활용 예시 (0) | 2025.03.03 |
| [Next.js] SSR에서의 Streaming HTML (0) | 2025.02.24 |