function outer() {
const name = "React";
function inner() {
console.log(name); // outer의 변수 name 참조
}
return inner;
}
const fn = outer(); // inner 함수 반환
fn(); // "React" 출력
inner
함수는 outer
함수의 변수를 기억한다.React의 함수형 컴포넌트는 사실 단순한 함수이다.
하지만 중요한 점은
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
console.log(count); // 현재 렌더 시점의 count 기억
}
return <button onClick={handleClick}>Click</button>;
}
handleClick
은 “버튼이 눌렸을 때 실행되는 함수”인데, 실제로는 생성 당시의 count 값을 클로저로 기억한다.클로저 트랩이란? 최신 값이 아니라, 과거 렌더링 때의 값만 계속 참조하는 상황을 뜻한다.
function Timer() {
const [count, setCount] = useState(0);
const startTimer = () => {
setTimeout(() => {
console.log(count); // ❌ 오래된 count 출력
}, 3000);
};
return (
<><button onClick={startTimer}>Start Timer</button>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</>
);
}
setTimeout
안의 함수가 생성될 때의 클로저를 참조하기 때문1) 의존성 배열 올바르게 작성
useEffect
, useCallback
같은 훅은 의존성 배열을 기반으로 동작한다. 의존성을 정확히 넣어야 매번 새 클로저로 갱신된다.
useEffect(() => {
const interval = setInterval(() => {
console.log(count); // ✅ 최신 count 출력
}, 1000);
return () => clearInterval(interval);
}, [count]); // count가 바뀌면 effect 재실행
2) useRef로 최신 값 저장
useRef
는 컴포넌트가 리렌더링되어도 동일한 객체를 유지한다. .current
값을 갱신하면 항상 최신 값에 접근 가능하다.
const countRef = useRef(count);
useEffect(() => {
countRef.current = count; // 매 렌더마다 최신값 저장
}, [count]);
useEffect(() => {
const id = setInterval(() => {
console.log(countRef.current); // ✅ 최신 값 참조
}, 1000);
return () => clearInterval(id);
}, []);
3) 함수형 업데이트로 안전하게 상태 변경
콜백 내부에서 오래된 값을 참조할 위험이 있다면, 이전 값(prev)을 기준으로 업데이트하면 된다.
setTimeout(() => {
setCount(prev => prev + 1); // ✅ 항상 최신 상태 기준으로 업데이트
}, 2000);
useRef
로 최신 값 보관prev => ...
) 활용